Compare commits
26 Commits
b219c5df0e
...
beb6529e55
Author | SHA1 | Date |
---|---|---|
timothycarambat | beb6529e55 | |
timothycarambat | 6e80f50f6e | |
timothycarambat | a727235195 | |
timothycarambat | d1664f3042 | |
timothycarambat | 990569e85b | |
timothycarambat | 146385bf41 | |
timothycarambat | 8bf0691f11 | |
timothycarambat | 2b92ae0901 | |
timothycarambat | 5145499776 | |
timothycarambat | 51765cfe97 | |
timothycarambat | 7fdd5afab3 | |
timothycarambat | cccec69293 | |
timothycarambat | 9fc45daf77 | |
timothycarambat | 27381a612c | |
timothycarambat | 20cfb2f4c4 | |
timothycarambat | 8e0b08ecad | |
Sean Hatfield | 9d410496c0 | |
Alex Leventer | d927629c19 | |
Timothy Carambat | dfab14a5d2 | |
Sean Hatfield | 9d41ff58e2 | |
Hakeem Abbas | 5614e2ed30 | |
Sean Hatfield | 21653b09fc | |
timothycarambat | 39d07feaed | |
timothycarambat | ca3decf413 | |
timothycarambat | 978cad476a | |
Timothy Carambat | 8377600211 |
|
@ -5,11 +5,8 @@ labels: [possible bug]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Use this template to file a bug report for AnythingLLM. Please be as descriptive as possible to allow everyone to replicate and solve your issue.
|
Use this template to file a bug report for AnythingLLM. Please be as descriptive as possible to allow everyone to replicate and solve your issue. Want help contributing a PR? Use our repo chatbot by OnboardAI! https://learnthisrepo.com/anythingllm"
|
||||||
|
|
||||||
Want help contributing a PR? Use our repo chatbot by OnboardAI! https://learnthisrepo.com/anythingllm
|
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: runtime
|
id: runtime
|
||||||
attributes:
|
attributes:
|
||||||
|
|
|
@ -20,6 +20,7 @@ on:
|
||||||
- '.vscode/**/*'
|
- '.vscode/**/*'
|
||||||
- '**/.env.example'
|
- '**/.env.example'
|
||||||
- '.github/ISSUE_TEMPLATE/**/*'
|
- '.github/ISSUE_TEMPLATE/**/*'
|
||||||
|
- 'embed/**/*' # Embed should be published to frontend (yarn build:publish) if any changes are introduced
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_multi_platform_to_registries:
|
push_multi_platform_to_registries:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"anythingllm",
|
"anythingllm",
|
||||||
|
"Astra",
|
||||||
"Dockerized",
|
"Dockerized",
|
||||||
"Embeddable",
|
"Embeddable",
|
||||||
"hljs",
|
"hljs",
|
||||||
|
|
|
@ -23,7 +23,12 @@ Here you can find the scripts and known working process to run AnythingLLM outsi
|
||||||
|
|
||||||
2. `cd anything-llm` and run `yarn setup`. This will install all dependencies to run in production as well as debug the application.
|
2. `cd anything-llm` and run `yarn setup`. This will install all dependencies to run in production as well as debug the application.
|
||||||
|
|
||||||
3. `cp server/.env.example server/.env` to create the basic ENV file for where instance settings will be read from on service start. This file is automatically managed and should not be edited manually.
|
3. `cp server/.env.example server/.env` to create the basic ENV file for where instance settings will be read from on service start.
|
||||||
|
|
||||||
|
4. Ensure that the `server/.env` file has _at least_ these keys to start. These values will persist and this file will be automatically written and managed after your first successful boot.
|
||||||
|
```
|
||||||
|
STORAGE_DIR="/your/absolute/path/to/server/.env"
|
||||||
|
```
|
||||||
|
|
||||||
## To start the application
|
## To start the application
|
||||||
|
|
||||||
|
@ -45,10 +50,10 @@ cd server && npx prisma migrate deploy --schema=./prisma/schema.prisma
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Boot the server in production
|
4. Boot the server in production
|
||||||
`cd server && NODE_ENV=production index.js &`
|
`cd server && NODE_ENV=production node index.js &`
|
||||||
|
|
||||||
5. Boot the collection in another process
|
5. Boot the collection in another process
|
||||||
`cd collector && NODE_ENV=production index.js &`
|
`cd collector && NODE_ENV=production node index.js &`
|
||||||
|
|
||||||
AnythingLLM should now be running on `http://localhost:3001`!
|
AnythingLLM should now be running on `http://localhost:3001`!
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@ Some cool features of AnythingLLM
|
||||||
**Supported Vector Databases:**
|
**Supported Vector Databases:**
|
||||||
|
|
||||||
- [LanceDB](https://github.com/lancedb/lancedb) (default)
|
- [LanceDB](https://github.com/lancedb/lancedb) (default)
|
||||||
|
- [Astra DB](https://www.datastax.com/products/datastax-astra)
|
||||||
- [Pinecone](https://pinecone.io)
|
- [Pinecone](https://pinecone.io)
|
||||||
- [Chroma](https://trychroma.com)
|
- [Chroma](https://trychroma.com)
|
||||||
- [Weaviate](https://weaviate.io)
|
- [Weaviate](https://weaviate.io)
|
||||||
|
|
|
@ -54,6 +54,7 @@ GID='1000'
|
||||||
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
|
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
|
||||||
# EMBEDDING_ENGINE='openai'
|
# EMBEDDING_ENGINE='openai'
|
||||||
# OPEN_AI_KEY=sk-xxxx
|
# OPEN_AI_KEY=sk-xxxx
|
||||||
|
# EMBEDDING_MODEL_PREF='text-embedding-ada-002'
|
||||||
|
|
||||||
# EMBEDDING_ENGINE='azure'
|
# EMBEDDING_ENGINE='azure'
|
||||||
# AZURE_OPENAI_ENDPOINT=
|
# AZURE_OPENAI_ENDPOINT=
|
||||||
|
@ -103,6 +104,11 @@ GID='1000'
|
||||||
# ZILLIZ_ENDPOINT="https://sample.api.gcp-us-west1.zillizcloud.com"
|
# ZILLIZ_ENDPOINT="https://sample.api.gcp-us-west1.zillizcloud.com"
|
||||||
# ZILLIZ_API_TOKEN=api-token-here
|
# ZILLIZ_API_TOKEN=api-token-here
|
||||||
|
|
||||||
|
# Enable all below if you are using vector database: Astra DB.
|
||||||
|
# VECTOR_DB="astra"
|
||||||
|
# ASTRA_DB_APPLICATION_TOKEN=
|
||||||
|
# ASTRA_DB_ENDPOINT=
|
||||||
|
|
||||||
# CLOUD DEPLOYMENT VARIRABLES ONLY
|
# CLOUD DEPLOYMENT VARIRABLES ONLY
|
||||||
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,4 @@
|
||||||
|
|
||||||
**/dist
|
**/dist
|
||||||
**/static/**
|
**/static/**
|
||||||
|
src/utils/chat/hljs.js
|
||||||
|
|
|
@ -1,3 +1,86 @@
|
||||||
# AnythingLLM Embedded Mode
|
# AnythingLLM Embedded Chat Widget
|
||||||
|
|
||||||
This folder of AnythingLLM contains the source code for how the embedded version of AnythingLLM works to provide a public facing interface of your workspace.
|
> [!WARNING]
|
||||||
|
> The core AnythingLLM team publishes a pre-built version of the script that is bundled
|
||||||
|
> with the main application. You can find it at the frontend URL `/embed/anythingllm-chat-widget.min.js`.
|
||||||
|
> You should only be working in this repo if you are wanting to build your own custom embed.
|
||||||
|
|
||||||
|
This folder of AnythingLLM contains the source code for how the embedded version of AnythingLLM works to provide a public facing interface of your workspace.
|
||||||
|
|
||||||
|
The AnythingLLM Embedded chat widget allows you to expose a workspace and its embedded knowledge base as a chat bubble via a `<script>` or `<iframe>` element that you can embed in a website or HTML.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Users will _not_ be able to view or read context snippets like they can in the core AnythingLLM application
|
||||||
|
- Users are assigned a random session ID that they use to persist a chat session.
|
||||||
|
- **Recommended** You can limit both the number of chats an embedding can process **and** per-session.
|
||||||
|
|
||||||
|
_by using the AnythingLLM embedded chat widget you are responsible for securing and configuration of the embed as to not allow excessive chat model abuse of your instance_
|
||||||
|
|
||||||
|
### Developer Setup
|
||||||
|
- `cd embed` from the root of the repo
|
||||||
|
- `yarn` to install all dev and script dependencies
|
||||||
|
- `yarn dev` to boot up an example HTML page to use the chat embed widget.
|
||||||
|
|
||||||
|
While in development mode (`yarn dev`) the script will rebuild on any changes to files in the `src` directory. Ensure that the required keys for the development embed are accurate and set.
|
||||||
|
|
||||||
|
`yarn build` will compile and minify your build of the script. You can then host and link your built script wherever you like.
|
||||||
|
|
||||||
|
## Integrations & Embed Types
|
||||||
|
|
||||||
|
### `<script>` tag HTML embed
|
||||||
|
|
||||||
|
The primary way of embedding a workspace as a chat widget is via a simple `<script>`
|
||||||
|
```html
|
||||||
|
<!--
|
||||||
|
An example of a script tag embed
|
||||||
|
REQUIRED data attributes:
|
||||||
|
data-embed-id // The unique id of your embed with its default settings
|
||||||
|
data-base-api-url // The URL of your anythingLLM instance backend
|
||||||
|
-->
|
||||||
|
<script
|
||||||
|
data-embed-id="5fc05aaf-2f2c-4c84-87a3-367a4692c1ee"
|
||||||
|
data-base-api-url="http://localhost:3001/api/embed"
|
||||||
|
src="http://localhost:3000/embed/anythingllm-chat-widget.min.js">
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `<script>` Customization Options
|
||||||
|
|
||||||
|
**LLM Overrides**
|
||||||
|
- `data-prompt` — Override the chat window with a custom system prompt. This is not visible to the user. If undefined it will use the embeds attached workspace system prompt.
|
||||||
|
|
||||||
|
- `data-model` — Override the chat model used for responses. This must be a valid model string for your AnythingLLM LLM provider. If unset it will use the embeds attached workspace model selection or the system setting.
|
||||||
|
|
||||||
|
- `data-temperature` — Override the chat model temperature. This must be a valid value for your AnythingLLM LLM provider. If unset it will use the embeds attached workspace model temperature or the system setting.
|
||||||
|
|
||||||
|
**Style Overrides**
|
||||||
|
- `data-chat-icon` — The chat bubble icon show when chat is closed. Options are `plus`, `chatCircle`, `support`, `search2`, `search`, `magic`.
|
||||||
|
|
||||||
|
- `data-button-color` — The chat bubble background color shown when chat is closed. Value must be hex color code.
|
||||||
|
|
||||||
|
- `data-user-bg-color` — The background color of the user chat bubbles when chatting. Value must be hex color code.
|
||||||
|
|
||||||
|
- `data-assistant-bg-color` — The background color of the assistant response chat bubbles when chatting. Value must be hex color code.
|
||||||
|
|
||||||
|
- `data-brand-image-url` — URL to image that will be show at the top of the chat when chat is open.
|
||||||
|
|
||||||
|
- `data-greeting` — Default text message to be shown when chat is opened and no previous message history is found.
|
||||||
|
|
||||||
|
- `data-no-sponsor` — Setting this attribute to anything will hide the custom or default sponsor at the bottom of an open chat window.
|
||||||
|
|
||||||
|
- `data-sponsor-link` — A clickable link in the sponsor section in the footer of an open chat window.
|
||||||
|
|
||||||
|
- `data-sponsor-text` — The text displays in sponsor text in the footer of an open chat window.
|
||||||
|
|
||||||
|
|
||||||
|
**Behavior Overrides**
|
||||||
|
- `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user.
|
||||||
|
|
||||||
|
- `data-support-email` — Shows a support email that the user can used to draft an email via the "three dot" menu in the top right. Option will not appear if it is not set.
|
||||||
|
|
||||||
|
|
||||||
|
### `<iframe>` tag HTML embed
|
||||||
|
_work in progress_
|
||||||
|
|
||||||
|
### `<iframe>` Customization Options
|
||||||
|
_work in progress_
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>This is an example testing page for embedded AnythingLLM.</h1>
|
<h1>This is an example testing page for embedded AnythingLLM.</h1>
|
||||||
<!-- <script data-embed-id="example-uuid" data-base-api-url='http://localhost:3001/api/embed' data-open-on-load="on"
|
<!--
|
||||||
src="/dist/anythingllm-chat-widget.js">
|
<script data-embed-id="example-uuid" data-base-api-url='http://localhost:3001/api/embed' data-open-on-load="on"
|
||||||
</script> -->
|
src="/dist/anythingllm-chat-widget.js"> USE THIS SRC FOR DEVELOPMENT SO CHANGES APPEAR!
|
||||||
|
</script>
|
||||||
|
-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "anythingllm-embedded-chat",
|
"name": "anythingllm-embedded-chat",
|
||||||
"private": true,
|
"private": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon -e js,jsx,css --watch src --exec \"yarn run dev:preview\"",
|
"dev": "nodemon -e js,jsx,css --watch src --exec \"yarn run dev:preview\"",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
"@phosphor-icons/react": "^2.0.13",
|
"@phosphor-icons/react": "^2.0.13",
|
||||||
"dompurify": "^3.0.8",
|
"dompurify": "^3.0.8",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
|
"highlight.js": "^11.9.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@ -39,4 +40,4 @@
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-plugin-singlefile": "^0.13.5"
|
"vite-plugin-singlefile": "^0.13.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// What is this script?
|
||||||
|
// We want to support code syntax highlighting in the embed modal, but we cannot afford to have the static build
|
||||||
|
// be large in size. To prevent HighlightJs from loading all 193+ language stylings and bloating the script, we instead take a large subset that
|
||||||
|
// covers most languages and then dynamically build and register each language since HLJS cannot just register with an array of langs.
|
||||||
|
// Since the embed is a single script - we need to statically import each library and register that associate language.
|
||||||
|
// we can then export this as a custom implementation of HLJS and call it a day and keep the bundle small.
|
||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
const SUPPORTED_HIGHLIGHTS = ['apache', 'bash', 'c', 'cpp', 'csharp', 'css', 'diff', 'go', 'graphql', 'ini', 'java', 'javascript', 'json', 'kotlin', 'less', 'lua', 'makefile', 'markdown', 'nginx', 'objectivec', 'perl', 'pgsql', 'php', 'php-template', 'plaintext', 'python', 'python-repl', 'r', 'ruby', 'rust', 'scss', 'shell', 'sql', 'swift', 'typescript', 'vbnet', 'wasm', 'xml', 'yaml'];
|
||||||
|
function quickClean(input) {
|
||||||
|
return input.replace(/[^a-zA-Z0-9]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = `/*
|
||||||
|
This is a dynamically generated file to help de-bloat the app since this script is a static bundle.
|
||||||
|
You should not modify this file directly. You can regenerate it with "node scripts/updateHljs.mjd" from the embed folder.
|
||||||
|
Last generated ${(new Date).toDateString()}
|
||||||
|
----------------------
|
||||||
|
*/\n\n`
|
||||||
|
content += 'import hljs from "highlight.js/lib/core";\n';
|
||||||
|
|
||||||
|
SUPPORTED_HIGHLIGHTS.forEach((lang) => {
|
||||||
|
content += `import ${quickClean(lang)}HljsSupport from 'highlight.js/lib/languages/${lang}'\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
SUPPORTED_HIGHLIGHTS.forEach((lang) => {
|
||||||
|
content += ` hljs.registerLanguage('${lang}', ${quickClean(lang)}HljsSupport)\n`;
|
||||||
|
})
|
||||||
|
|
||||||
|
content += `// The above should now register on the languages we wish to support statically.\n`;
|
||||||
|
content += `export const staticHljs = hljs;\n`
|
||||||
|
|
||||||
|
fs.writeFileSync('src/utils/chat/hljs.js', content, { encoding: 'utf8' })
|
||||||
|
console.log(`Static build of HLJS completed - src/utils/chat/hljs.js`)
|
|
@ -12,7 +12,9 @@ export default function App() {
|
||||||
const sessionId = useSessionId();
|
const sessionId = useSessionId();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
toggleOpenChat(embedSettings.openOnLoad === "on");
|
if (embedSettings.openOnLoad === "on") {
|
||||||
|
toggleOpenChat(true);
|
||||||
|
}
|
||||||
}, [embedSettings.loaded]);
|
}, [embedSettings.loaded]);
|
||||||
|
|
||||||
if (!embedSettings.loaded) return null;
|
if (!embedSettings.loaded) return null;
|
||||||
|
@ -25,9 +27,9 @@ export default function App() {
|
||||||
width: isChatOpen ? 320 : "auto",
|
width: isChatOpen ? 320 : "auto",
|
||||||
height: isChatOpen ? "93vh" : "auto",
|
height: isChatOpen ? "93vh" : "auto",
|
||||||
}}
|
}}
|
||||||
className={`transition-all duration-300 ease-in-out ${
|
className={`${
|
||||||
isChatOpen
|
isChatOpen
|
||||||
? "max-w-md p-4 bg-white rounded-lg border shadow-lg w-72"
|
? "max-w-md px-4 py-2 bg-white rounded-lg border shadow-lg w-72"
|
||||||
: "w-16 h-16 rounded-full"
|
: "w-16 h-16 rounded-full"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -41,7 +43,7 @@ export default function App() {
|
||||||
<OpenButton
|
<OpenButton
|
||||||
settings={embedSettings}
|
settings={embedSettings}
|
||||||
isOpen={isChatOpen}
|
isOpen={isChatOpen}
|
||||||
toggleOpen={toggleOpenChat}
|
toggleOpen={() => toggleOpenChat(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { memo, forwardRef } from "react";
|
||||||
import { Warning } from "@phosphor-icons/react";
|
import { Warning } from "@phosphor-icons/react";
|
||||||
// import Actions from "./Actions";
|
// import Actions from "./Actions";
|
||||||
import renderMarkdown from "@/utils/chat/markdown";
|
import renderMarkdown from "@/utils/chat/markdown";
|
||||||
import { AI_BACKGROUND_COLOR, USER_BACKGROUND_COLOR } from "@/utils/constants";
|
import { embedderSettings } from "@/main";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import createDOMPurify from "dompurify";
|
import createDOMPurify from "dompurify";
|
||||||
|
|
||||||
|
@ -17,11 +17,14 @@ const HistoricalMessage = forwardRef(
|
||||||
error
|
error
|
||||||
? "bg-red-200"
|
? "bg-red-200"
|
||||||
: role === "user"
|
: role === "user"
|
||||||
? USER_BACKGROUND_COLOR
|
? embedderSettings.USER_BACKGROUND_COLOR
|
||||||
: AI_BACKGROUND_COLOR
|
: embedderSettings.AI_BACKGROUND_COLOR
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className={`py-2 px-2 w-full flex flex-col`}>
|
<div
|
||||||
|
style={{ wordBreak: "break-word" }}
|
||||||
|
className={`py-2 px-2 w-full flex flex-col`}
|
||||||
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="p-2 rounded-lg bg-red-50 text-red-500">
|
<div className="p-2 rounded-lg bg-red-50 text-red-500">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { forwardRef, memo } from "react";
|
import { forwardRef, memo } from "react";
|
||||||
import { Warning } from "@phosphor-icons/react";
|
import { Warning } from "@phosphor-icons/react";
|
||||||
import renderMarkdown from "@/utils/chat/markdown";
|
import renderMarkdown from "@/utils/chat/markdown";
|
||||||
import { AI_BACKGROUND_COLOR } from "@/utils/constants";
|
import { embedderSettings } from "@/main";
|
||||||
|
|
||||||
const PromptReply = forwardRef(
|
const PromptReply = forwardRef(
|
||||||
({ uuid, reply, pending, error, sources = [] }, ref) => {
|
({ uuid, reply, pending, error, sources = [] }, ref) => {
|
||||||
|
@ -11,7 +11,7 @@ const PromptReply = forwardRef(
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`flex justify-center items-end rounded-lg w-full ${AI_BACKGROUND_COLOR}`}
|
className={`flex justify-center items-end rounded-lg w-full ${embedderSettings.AI_BACKGROUND_COLOR}`}
|
||||||
>
|
>
|
||||||
<div className="py-2 px-2 w-full flex flex-col">
|
<div className="py-2 px-2 w-full flex flex-col">
|
||||||
<div className="flex gap-x-5">
|
<div className="flex gap-x-5">
|
||||||
|
@ -44,9 +44,12 @@ const PromptReply = forwardRef(
|
||||||
<div
|
<div
|
||||||
key={uuid}
|
key={uuid}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={`flex justify-center items-end w-full ${AI_BACKGROUND_COLOR}`}
|
className={`flex justify-center items-end w-full ${embedderSettings.AI_BACKGROUND_COLOR}`}
|
||||||
>
|
>
|
||||||
<div className="py-2 px-2 w-full flex flex-col">
|
<div
|
||||||
|
style={{ wordBreak: "break-word" }}
|
||||||
|
className="py-2 px-2 w-full flex flex-col"
|
||||||
|
>
|
||||||
<div className="flex gap-x-5">
|
<div className="flex gap-x-5">
|
||||||
<span
|
<span
|
||||||
className={`reply whitespace-pre-line text-white font-normal text-sm md:text-sm flex flex-col gap-y-1 mt-2`}
|
className={`reply whitespace-pre-line text-white font-normal text-sm md:text-sm flex flex-col gap-y-1 mt-2`}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import HistoricalMessage from "./HistoricalMessage";
|
import HistoricalMessage from "./HistoricalMessage";
|
||||||
import PromptReply from "./PromptReply";
|
import PromptReply from "./PromptReply";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ArrowDown } from "@phosphor-icons/react";
|
import { ArrowDown, CircleNotch } from "@phosphor-icons/react";
|
||||||
import debounce from "lodash.debounce";
|
import debounce from "lodash.debounce";
|
||||||
|
|
||||||
export default function ChatHistory({ settings = {}, history = [] }) {
|
export default function ChatHistory({ settings = {}, history = [] }) {
|
||||||
|
@ -46,10 +46,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
||||||
|
|
||||||
if (history.length === 0) {
|
if (history.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="h-full max-h-[82vh] pb-[100px] pt-[5px] bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll">
|
||||||
style={{ height: "85vh", paddingBottom: 100, paddingTop: 5 }}
|
|
||||||
className="bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll"
|
|
||||||
>
|
|
||||||
<div className="flex h-full flex-col items-center justify-center">
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
<p className="text-slate-400 text-sm font-base py-4 text-center">
|
<p className="text-slate-400 text-sm font-base py-4 text-center">
|
||||||
{settings?.greeting ?? "Send a chat to get started!"}
|
{settings?.greeting ?? "Send a chat to get started!"}
|
||||||
|
@ -61,8 +58,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ height: "85vh", paddingBottom: 100, paddingTop: 5 }}
|
className="h-full max-h-[82vh] pb-[100px] pt-[5px] bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll"
|
||||||
className="bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll"
|
|
||||||
id="chat-history"
|
id="chat-history"
|
||||||
ref={chatHistoryRef}
|
ref={chatHistoryRef}
|
||||||
>
|
>
|
||||||
|
@ -98,7 +94,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isAtBottom && (
|
{!isAtBottom && (
|
||||||
<div className="fixed bottom-[8rem] right-[3rem] z-50 cursor-pointer animate-pulse">
|
<div className="fixed bottom-[10rem] right-[3rem] z-50 cursor-pointer animate-pulse">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="p-1 rounded-full border border-white/10 bg-white/10 hover:bg-white/20 hover:text-white">
|
<div className="p-1 rounded-full border border-white/10 bg-white/10 hover:bg-white/20 hover:text-white">
|
||||||
<ArrowDown
|
<ArrowDown
|
||||||
|
@ -113,3 +109,15 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ChatHistoryLoading() {
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full relative">
|
||||||
|
<div className="h-full max-h-[82vh] pb-[100px] pt-[5px] bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll">
|
||||||
|
<div className="flex h-full flex-col items-center justify-center">
|
||||||
|
<CircleNotch size={14} className="text-slate-400 animate-spin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -33,15 +33,12 @@ export default function PromptInput({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="w-full absolute left-0 bottom-[5px] z-10 flex justify-center items-center">
|
||||||
style={{ bottom: 25 }}
|
|
||||||
className="w-full fixed md:absolute left-0 z-10 flex justify-center items-center"
|
|
||||||
>
|
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl"
|
className="flex flex-col gap-y-1 rounded-t-lg w-full items-center justify-center"
|
||||||
>
|
>
|
||||||
<div className="flex items-center rounded-lg md:mb-4">
|
<div className="flex items-center rounded-lg">
|
||||||
<div className="bg-white border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
|
<div className="bg-white border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
|
||||||
<div className="flex items-center w-full">
|
<div className="flex items-center w-full">
|
||||||
<textarea
|
<textarea
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default function ChatContainer({
|
||||||
}, [loadingResponse, chatHistory]);
|
}, [loadingResponse, chatHistory]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<div className="h-full w-full relative">
|
||||||
<ChatHistory settings={settings} history={chatHistory} />
|
<ChatHistory settings={settings} history={chatHistory} />
|
||||||
<PromptInput
|
<PromptInput
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
@ -86,6 +86,6 @@ export default function ChatContainer({
|
||||||
inputDisabled={loadingResponse}
|
inputDisabled={loadingResponse}
|
||||||
buttonDisabled={loadingResponse}
|
buttonDisabled={loadingResponse}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import AnythingLLMLogo from "@/assets/anything-llm-dark.png";
|
import AnythingLLMLogo from "@/assets/anything-llm-dark.png";
|
||||||
import ChatService from "@/models/chatService";
|
import ChatService from "@/models/chatService";
|
||||||
import { DotsThreeOutlineVertical, Lightning, X } from "@phosphor-icons/react";
|
import {
|
||||||
|
DotsThreeOutlineVertical,
|
||||||
|
Envelope,
|
||||||
|
Lightning,
|
||||||
|
X,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function ChatWindowHeader({
|
export default function ChatWindowHeader({
|
||||||
|
@ -46,15 +51,19 @@ export default function ChatWindowHeader({
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<OptionsMenu showing={showingOptions} resetChat={handleChatReset} />
|
<OptionsMenu
|
||||||
|
settings={settings}
|
||||||
|
showing={showingOptions}
|
||||||
|
resetChat={handleChatReset}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OptionsMenu({ showing, resetChat }) {
|
function OptionsMenu({ settings, showing, resetChat }) {
|
||||||
if (!showing) return null;
|
if (!showing) return null;
|
||||||
return (
|
return (
|
||||||
<div className="absolute bg-white flex flex-col gap-y-2 rounded-lg shadow-lg border border-gray-300 top-[3vh] right-[1vw] max-w-[150px]">
|
<div className="absolute z-10 bg-white flex flex-col gap-y-1 rounded-lg shadow-lg border border-gray-300 top-[23px] right-[20px] max-w-[150px]">
|
||||||
<button
|
<button
|
||||||
onClick={resetChat}
|
onClick={resetChat}
|
||||||
className="flex items-center gap-x-1 hover:bg-gray-100 text-sm text-gray-700 p-2 rounded-lg"
|
className="flex items-center gap-x-1 hover:bg-gray-100 text-sm text-gray-700 p-2 rounded-lg"
|
||||||
|
@ -62,6 +71,22 @@ function OptionsMenu({ showing, resetChat }) {
|
||||||
<Lightning size={14} />
|
<Lightning size={14} />
|
||||||
<p>Reset Chat</p>
|
<p>Reset Chat</p>
|
||||||
</button>
|
</button>
|
||||||
|
<ContactSupport email={settings.supportEmail} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContactSupport({ email = null }) {
|
||||||
|
if (!email) return null;
|
||||||
|
|
||||||
|
const subject = `Inquiry from ${window.location.origin}`;
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={`mailto:${email}?Subject=${encodeURIComponent(subject)}`}
|
||||||
|
className="flex items-center gap-x-1 hover:bg-gray-100 text-sm text-gray-700 p-2 rounded-lg"
|
||||||
|
>
|
||||||
|
<Envelope size={14} />
|
||||||
|
<p>Email support</p>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import ChatWindowHeader from "./Header";
|
||||||
import SessionId from "../SessionId";
|
import SessionId from "../SessionId";
|
||||||
import useChatHistory from "@/hooks/chat/useChatHistory";
|
import useChatHistory from "@/hooks/chat/useChatHistory";
|
||||||
import ChatContainer from "./ChatContainer";
|
import ChatContainer from "./ChatContainer";
|
||||||
|
import Sponsor from "../Sponsor";
|
||||||
|
import { ChatHistoryLoading } from "./ChatContainer/ChatHistory";
|
||||||
|
|
||||||
export default function ChatWindow({ closeChat, settings, sessionId }) {
|
export default function ChatWindow({ closeChat, settings, sessionId }) {
|
||||||
const { chatHistory, setChatHistory, loading } = useChatHistory(
|
const { chatHistory, setChatHistory, loading } = useChatHistory(
|
||||||
|
@ -9,10 +11,28 @@ export default function ChatWindow({ closeChat, settings, sessionId }) {
|
||||||
sessionId
|
sessionId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) return null;
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<ChatWindowHeader
|
||||||
|
sessionId={sessionId}
|
||||||
|
settings={settings}
|
||||||
|
iconUrl={settings.brandImageUrl}
|
||||||
|
closeChat={closeChat}
|
||||||
|
setChatHistory={setChatHistory}
|
||||||
|
/>
|
||||||
|
<ChatHistoryLoading />
|
||||||
|
<div className="pt-4 pb-2 h-fit gap-y-1">
|
||||||
|
<SessionId />
|
||||||
|
<Sponsor settings={settings} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setEventDelegatorForCodeSnippets();
|
setEventDelegatorForCodeSnippets();
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col h-full">
|
||||||
<ChatWindowHeader
|
<ChatWindowHeader
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
|
@ -25,7 +45,10 @@ export default function ChatWindow({ closeChat, settings, sessionId }) {
|
||||||
settings={settings}
|
settings={settings}
|
||||||
knownHistory={chatHistory}
|
knownHistory={chatHistory}
|
||||||
/>
|
/>
|
||||||
<SessionId />
|
<div className="pt-4 pb-2 h-fit gap-y-1">
|
||||||
|
<SessionId />
|
||||||
|
<Sponsor settings={settings} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,14 +159,6 @@ const customCss = `
|
||||||
.bg-black-900 {
|
.bg-black-900 {
|
||||||
background: #141414;
|
background: #141414;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-historical-msg-system {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-historical-msg-user {
|
|
||||||
background: #2C2F35;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function Head() {
|
export default function Head() {
|
||||||
|
|
|
@ -9,10 +9,10 @@ import {
|
||||||
|
|
||||||
const CHAT_ICONS = {
|
const CHAT_ICONS = {
|
||||||
plus: Plus,
|
plus: Plus,
|
||||||
"chat-circle-dots": ChatCircleDots,
|
chatBubble: ChatCircleDots,
|
||||||
headset: Headset,
|
support: Headset,
|
||||||
binoculars: Binoculars,
|
search2: Binoculars,
|
||||||
magnifying: MagnifyingGlass,
|
search: MagnifyingGlass,
|
||||||
magic: MagicWand,
|
magic: MagicWand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ export default function OpenButton({ settings, isOpen, toggleOpen }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={toggleOpen}
|
onClick={toggleOpen}
|
||||||
className="flex items-center justify-center p-4 rounded-full bg-blue-500 text-white text-2xl"
|
className={`flex items-center justify-center p-4 rounded-full bg-[${settings.buttonColor}] text-white text-2xl`}
|
||||||
aria-label="Toggle Menu"
|
aria-label="Toggle Menu"
|
||||||
>
|
>
|
||||||
<ChatIcon className="text-white" />
|
<ChatIcon className="text-white" />
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
export default function Sponsor({ settings }) {
|
||||||
|
if (!!settings.noSponsor) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full items-center justify-center">
|
||||||
|
<a
|
||||||
|
href={settings.sponsorLink ?? "#"}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-xs text-gray-300 hover:text-blue-300 hover:underline"
|
||||||
|
>
|
||||||
|
{settings.sponsorText}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,16 @@
|
||||||
|
import { CHAT_UI_REOPEN } from "@/utils/constants";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function useOpenChat() {
|
export default function useOpenChat() {
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(
|
||||||
|
!!window?.localStorage?.getItem(CHAT_UI_REOPEN) || false
|
||||||
|
);
|
||||||
|
|
||||||
//TODO: Detect if chat was previously open??
|
function toggleOpenChat(newValue) {
|
||||||
return { isChatOpen: isOpen, toggleOpenChat: setOpen };
|
if (newValue === true) window.localStorage.setItem(CHAT_UI_REOPEN, "1");
|
||||||
|
if (newValue === false) window.localStorage.removeItem(CHAT_UI_REOPEN);
|
||||||
|
setOpen(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isChatOpen: isOpen, toggleOpenChat };
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,16 @@ const DEFAULT_SETTINGS = {
|
||||||
chatIcon: "plus",
|
chatIcon: "plus",
|
||||||
brandImageUrl: null, // will be forced into 100x50px container
|
brandImageUrl: null, // will be forced into 100x50px container
|
||||||
greeting: null, // empty chat window greeting.
|
greeting: null, // empty chat window greeting.
|
||||||
|
buttonColor: "#262626", // must be hex color code
|
||||||
|
userBgColor: "#2C2F35", // user text bubble color
|
||||||
|
assistantBgColor: "#2563eb", // assistant text bubble color
|
||||||
|
noSponsor: null, // Shows sponsor in footer of chat
|
||||||
|
sponsorText: "Powered by AnythingLLM", // default sponsor text
|
||||||
|
sponsorLink: "https://useanything.com", // default sponsor link
|
||||||
|
|
||||||
// behaviors
|
// behaviors
|
||||||
openOnLoad: "off", // or "on"
|
openOnLoad: "off", // or "on"
|
||||||
|
supportEmail: null, // string of email for contact
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useGetScriptAttributes() {
|
export default function useGetScriptAttributes() {
|
||||||
|
|
|
@ -11,6 +11,12 @@ root.render(
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scriptSettings = Object.assign(
|
||||||
|
{},
|
||||||
|
document?.currentScript?.dataset || {}
|
||||||
|
);
|
||||||
export const embedderSettings = {
|
export const embedderSettings = {
|
||||||
settings: Object.assign({}, document?.currentScript?.dataset || {}),
|
settings: scriptSettings,
|
||||||
|
USER_BACKGROUND_COLOR: `bg-[${scriptSettings?.userBgColor ?? "#2C2F35"}]`,
|
||||||
|
AI_BACKGROUND_COLOR: `bg-[${scriptSettings?.assistantBgColor ?? "#2563eb"}]`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
This is a dynamically generated file to help de-bloat the app since this script is a static bundle.
|
||||||
|
You should not modify this file directly. You can regenerate it with "node scripts/updateHljs.mjd" from the embed folder.
|
||||||
|
Last generated Fri Feb 02 2024
|
||||||
|
----------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import apacheHljsSupport from 'highlight.js/lib/languages/apache'
|
||||||
|
import bashHljsSupport from 'highlight.js/lib/languages/bash'
|
||||||
|
import cHljsSupport from 'highlight.js/lib/languages/c'
|
||||||
|
import cppHljsSupport from 'highlight.js/lib/languages/cpp'
|
||||||
|
import csharpHljsSupport from 'highlight.js/lib/languages/csharp'
|
||||||
|
import cssHljsSupport from 'highlight.js/lib/languages/css'
|
||||||
|
import diffHljsSupport from 'highlight.js/lib/languages/diff'
|
||||||
|
import goHljsSupport from 'highlight.js/lib/languages/go'
|
||||||
|
import graphqlHljsSupport from 'highlight.js/lib/languages/graphql'
|
||||||
|
import iniHljsSupport from 'highlight.js/lib/languages/ini'
|
||||||
|
import javaHljsSupport from 'highlight.js/lib/languages/java'
|
||||||
|
import javascriptHljsSupport from 'highlight.js/lib/languages/javascript'
|
||||||
|
import jsonHljsSupport from 'highlight.js/lib/languages/json'
|
||||||
|
import kotlinHljsSupport from 'highlight.js/lib/languages/kotlin'
|
||||||
|
import lessHljsSupport from 'highlight.js/lib/languages/less'
|
||||||
|
import luaHljsSupport from 'highlight.js/lib/languages/lua'
|
||||||
|
import makefileHljsSupport from 'highlight.js/lib/languages/makefile'
|
||||||
|
import markdownHljsSupport from 'highlight.js/lib/languages/markdown'
|
||||||
|
import nginxHljsSupport from 'highlight.js/lib/languages/nginx'
|
||||||
|
import objectivecHljsSupport from 'highlight.js/lib/languages/objectivec'
|
||||||
|
import perlHljsSupport from 'highlight.js/lib/languages/perl'
|
||||||
|
import pgsqlHljsSupport from 'highlight.js/lib/languages/pgsql'
|
||||||
|
import phpHljsSupport from 'highlight.js/lib/languages/php'
|
||||||
|
import phptemplateHljsSupport from 'highlight.js/lib/languages/php-template'
|
||||||
|
import plaintextHljsSupport from 'highlight.js/lib/languages/plaintext'
|
||||||
|
import pythonHljsSupport from 'highlight.js/lib/languages/python'
|
||||||
|
import pythonreplHljsSupport from 'highlight.js/lib/languages/python-repl'
|
||||||
|
import rHljsSupport from 'highlight.js/lib/languages/r'
|
||||||
|
import rubyHljsSupport from 'highlight.js/lib/languages/ruby'
|
||||||
|
import rustHljsSupport from 'highlight.js/lib/languages/rust'
|
||||||
|
import scssHljsSupport from 'highlight.js/lib/languages/scss'
|
||||||
|
import shellHljsSupport from 'highlight.js/lib/languages/shell'
|
||||||
|
import sqlHljsSupport from 'highlight.js/lib/languages/sql'
|
||||||
|
import swiftHljsSupport from 'highlight.js/lib/languages/swift'
|
||||||
|
import typescriptHljsSupport from 'highlight.js/lib/languages/typescript'
|
||||||
|
import vbnetHljsSupport from 'highlight.js/lib/languages/vbnet'
|
||||||
|
import wasmHljsSupport from 'highlight.js/lib/languages/wasm'
|
||||||
|
import xmlHljsSupport from 'highlight.js/lib/languages/xml'
|
||||||
|
import yamlHljsSupport from 'highlight.js/lib/languages/yaml'
|
||||||
|
hljs.registerLanguage('apache', apacheHljsSupport)
|
||||||
|
hljs.registerLanguage('bash', bashHljsSupport)
|
||||||
|
hljs.registerLanguage('c', cHljsSupport)
|
||||||
|
hljs.registerLanguage('cpp', cppHljsSupport)
|
||||||
|
hljs.registerLanguage('csharp', csharpHljsSupport)
|
||||||
|
hljs.registerLanguage('css', cssHljsSupport)
|
||||||
|
hljs.registerLanguage('diff', diffHljsSupport)
|
||||||
|
hljs.registerLanguage('go', goHljsSupport)
|
||||||
|
hljs.registerLanguage('graphql', graphqlHljsSupport)
|
||||||
|
hljs.registerLanguage('ini', iniHljsSupport)
|
||||||
|
hljs.registerLanguage('java', javaHljsSupport)
|
||||||
|
hljs.registerLanguage('javascript', javascriptHljsSupport)
|
||||||
|
hljs.registerLanguage('json', jsonHljsSupport)
|
||||||
|
hljs.registerLanguage('kotlin', kotlinHljsSupport)
|
||||||
|
hljs.registerLanguage('less', lessHljsSupport)
|
||||||
|
hljs.registerLanguage('lua', luaHljsSupport)
|
||||||
|
hljs.registerLanguage('makefile', makefileHljsSupport)
|
||||||
|
hljs.registerLanguage('markdown', markdownHljsSupport)
|
||||||
|
hljs.registerLanguage('nginx', nginxHljsSupport)
|
||||||
|
hljs.registerLanguage('objectivec', objectivecHljsSupport)
|
||||||
|
hljs.registerLanguage('perl', perlHljsSupport)
|
||||||
|
hljs.registerLanguage('pgsql', pgsqlHljsSupport)
|
||||||
|
hljs.registerLanguage('php', phpHljsSupport)
|
||||||
|
hljs.registerLanguage('php-template', phptemplateHljsSupport)
|
||||||
|
hljs.registerLanguage('plaintext', plaintextHljsSupport)
|
||||||
|
hljs.registerLanguage('python', pythonHljsSupport)
|
||||||
|
hljs.registerLanguage('python-repl', pythonreplHljsSupport)
|
||||||
|
hljs.registerLanguage('r', rHljsSupport)
|
||||||
|
hljs.registerLanguage('ruby', rubyHljsSupport)
|
||||||
|
hljs.registerLanguage('rust', rustHljsSupport)
|
||||||
|
hljs.registerLanguage('scss', scssHljsSupport)
|
||||||
|
hljs.registerLanguage('shell', shellHljsSupport)
|
||||||
|
hljs.registerLanguage('sql', sqlHljsSupport)
|
||||||
|
hljs.registerLanguage('swift', swiftHljsSupport)
|
||||||
|
hljs.registerLanguage('typescript', typescriptHljsSupport)
|
||||||
|
hljs.registerLanguage('vbnet', vbnetHljsSupport)
|
||||||
|
hljs.registerLanguage('wasm', wasmHljsSupport)
|
||||||
|
hljs.registerLanguage('xml', xmlHljsSupport)
|
||||||
|
hljs.registerLanguage('yaml', yamlHljsSupport)
|
||||||
|
// The above should now register on the languages we wish to support statically.
|
||||||
|
export const staticHljs = hljs;
|
|
@ -1,14 +1,31 @@
|
||||||
import { encode as HTMLEncode } from "he";
|
import { encode as HTMLEncode } from "he";
|
||||||
import markdownIt from "markdown-it";
|
import markdownIt from "markdown-it";
|
||||||
|
import { staticHljs as hljs } from "./hljs";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
// TODO can we add back Hljs without bloating the app in a bad way?
|
|
||||||
|
|
||||||
const markdown = markdownIt({
|
const markdown = markdownIt({
|
||||||
html: true,
|
html: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
highlight: function (code) {
|
highlight: function (code, lang) {
|
||||||
const uuid = v4();
|
const uuid = v4();
|
||||||
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
`<div class="whitespace-pre-line w-full rounded-lg bg-black-900 pb-4 relative font-mono font-normal text-sm text-slate-200">
|
||||||
|
<div class="w-full flex items-center absolute top-0 left-0 text-slate-200 bg-stone-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md">
|
||||||
|
<div class="flex gap-2"><code class="text-xs">${lang}</code></div>
|
||||||
|
<button data-code-snippet data-code="code-${uuid}" class="flex items-center gap-x-2">
|
||||||
|
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>
|
||||||
|
<p>Copy</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre class="whitespace-pre-wrap px-2">` +
|
||||||
|
hljs.highlight(code, { language: lang, ignoreIllegals: true }).value +
|
||||||
|
"</pre></div>"
|
||||||
|
);
|
||||||
|
} catch (__) {}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`<div class="whitespace-pre-line w-full rounded-lg bg-black-900 pb-4 relative font-mono font-normal text-sm text-slate-200">
|
`<div class="whitespace-pre-line w-full rounded-lg bg-black-900 pb-4 relative font-mono font-normal text-sm text-slate-200">
|
||||||
<div class="w-full flex items-center absolute top-0 left-0 text-slate-200 bg-stone-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md">
|
<div class="w-full flex items-center absolute top-0 left-0 text-slate-200 bg-stone-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md">
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
// export const USER_BACKGROUND_COLOR = "bg-gray-700";
|
export const CHAT_UI_REOPEN = "___anythingllm-chat-widget-open___";
|
||||||
// export const AI_BACKGROUND_COLOR = "bg-blue-600";
|
|
||||||
export const USER_BACKGROUND_COLOR = "bg-historical-msg-user";
|
|
||||||
export const AI_BACKGROUND_COLOR = "bg-historical-msg-system";
|
|
||||||
|
|
|
@ -1642,6 +1642,11 @@ he@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
|
highlight.js@^11.9.0:
|
||||||
|
version "11.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
|
||||||
|
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
|
||||||
|
|
||||||
human-signals@^2.1.0:
|
human-signals@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -44,7 +44,7 @@ const DataConnectorSetup = lazy(
|
||||||
const EmbedConfigSetup = lazy(
|
const EmbedConfigSetup = lazy(
|
||||||
() => import("@/pages/GeneralSettings/EmbedConfigs")
|
() => import("@/pages/GeneralSettings/EmbedConfigs")
|
||||||
);
|
);
|
||||||
const EmbedChats = lazy(() => import("@/pages/Admin/Users"));
|
const EmbedChats = lazy(() => import("@/pages/GeneralSettings/EmbedChats"));
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -22,12 +22,27 @@ export default function OpenAiOptions({ settings }) {
|
||||||
Model Preference
|
Model Preference
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
disabled={true}
|
name="EmbeddingModelPref"
|
||||||
className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
required={true}
|
||||||
|
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||||
>
|
>
|
||||||
<option disabled={true} selected={true}>
|
<optgroup label="Available embedding models">
|
||||||
text-embedding-ada-002
|
{[
|
||||||
</option>
|
"text-embedding-ada-002",
|
||||||
|
"text-embedding-3-small",
|
||||||
|
"text-embedding-3-large",
|
||||||
|
].map((model) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={model}
|
||||||
|
value={model}
|
||||||
|
selected={settings?.EmbeddingModelPref === model}
|
||||||
|
>
|
||||||
|
{model}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,6 +85,7 @@ function OpenAIModelSelection({ apiKey, settings }) {
|
||||||
"gpt-3.5-turbo",
|
"gpt-3.5-turbo",
|
||||||
"gpt-3.5-turbo-1106",
|
"gpt-3.5-turbo-1106",
|
||||||
"gpt-4",
|
"gpt-4",
|
||||||
|
"gpt-4-turbo-preview",
|
||||||
"gpt-4-1106-preview",
|
"gpt-4-1106-preview",
|
||||||
"gpt-4-32k",
|
"gpt-4-32k",
|
||||||
].map((model) => {
|
].map((model) => {
|
||||||
|
|
|
@ -6,9 +6,14 @@ import Directory from "./Directory";
|
||||||
import showToast from "../../../../utils/toast";
|
import showToast from "../../../../utils/toast";
|
||||||
import WorkspaceDirectory from "./WorkspaceDirectory";
|
import WorkspaceDirectory from "./WorkspaceDirectory";
|
||||||
|
|
||||||
// OpenAI Cost per token for text-ada-embedding
|
// OpenAI Cost per token
|
||||||
// ref: https://openai.com/pricing#:~:text=%C2%A0/%201K%20tokens-,Embedding%20models,-Build%20advanced%20search
|
// ref: https://openai.com/pricing#:~:text=%C2%A0/%201K%20tokens-,Embedding%20models,-Build%20advanced%20search
|
||||||
const COST_PER_TOKEN = 0.0000001; // $0.0001 / 1K tokens
|
|
||||||
|
const MODEL_COSTS = {
|
||||||
|
"text-embedding-ada-002": 0.0000001, // $0.0001 / 1K tokens
|
||||||
|
"text-embedding-3-small": 0.00000002, // $0.00002 / 1K tokens
|
||||||
|
"text-embedding-3-large": 0.00000013, // $0.00013 / 1K tokens
|
||||||
|
};
|
||||||
|
|
||||||
export default function DocumentSettings({
|
export default function DocumentSettings({
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -142,10 +147,12 @@ export default function DocumentSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do not do cost estimation unless the embedding engine is OpenAi.
|
// Do not do cost estimation unless the embedding engine is OpenAi.
|
||||||
if (
|
if (systemSettings?.EmbeddingEngine === "openai") {
|
||||||
!systemSettings?.EmbeddingEngine ||
|
const COST_PER_TOKEN =
|
||||||
systemSettings.EmbeddingEngine === "openai"
|
MODEL_COSTS[
|
||||||
) {
|
systemSettings?.EmbeddingModelPref || "text-embedding-ada-002"
|
||||||
|
];
|
||||||
|
|
||||||
const dollarAmount = (totalTokenCount / 1000) * COST_PER_TOKEN;
|
const dollarAmount = (totalTokenCount / 1000) * COST_PER_TOKEN;
|
||||||
setEmbeddingsCost(dollarAmount);
|
setEmbeddingsCost(dollarAmount);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ const PROVIDER_DEFAULT_MODELS = {
|
||||||
"gpt-3.5-turbo",
|
"gpt-3.5-turbo",
|
||||||
"gpt-3.5-turbo-1106",
|
"gpt-3.5-turbo-1106",
|
||||||
"gpt-4",
|
"gpt-4",
|
||||||
|
"gpt-4-turbo-preview",
|
||||||
"gpt-4-1106-preview",
|
"gpt-4-1106-preview",
|
||||||
"gpt-4-32k",
|
"gpt-4-32k",
|
||||||
],
|
],
|
||||||
|
|
|
@ -44,6 +44,7 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
||||||
const formEl = useRef(null);
|
const formEl = useRef(null);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
const defaults = recommendedSettings(settings?.LLMProvider);
|
const defaults = recommendedSettings(settings?.LLMProvider);
|
||||||
|
|
||||||
const handleUpdate = async (e) => {
|
const handleUpdate = async (e) => {
|
||||||
|
@ -72,7 +73,15 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
await Workspace.delete(workspace.slug);
|
|
||||||
|
setDeleting(true);
|
||||||
|
const success = await Workspace.delete(workspace.slug);
|
||||||
|
if (!success) {
|
||||||
|
showToast("Workspace could not be deleted!", "error", { clear: true });
|
||||||
|
setDeleting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
workspace.slug === slug
|
workspace.slug === slug
|
||||||
? (window.location = paths.home())
|
? (window.location = paths.home())
|
||||||
: window.location.reload();
|
: window.location.reload();
|
||||||
|
@ -310,7 +319,11 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-2 md:p-6 space-x-2 border-t rounded-b border-gray-600">
|
<div className="flex items-center justify-between p-2 md:p-6 space-x-2 border-t rounded-b border-gray-600">
|
||||||
<DeleteWorkspace workspace={workspace} onClick={deleteWorkspace} />
|
<DeleteWorkspace
|
||||||
|
deleting={deleting}
|
||||||
|
workspace={workspace}
|
||||||
|
onClick={deleteWorkspace}
|
||||||
|
/>
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -324,7 +337,7 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteWorkspace({ workspace, onClick }) {
|
function DeleteWorkspace({ deleting, workspace, onClick }) {
|
||||||
const [canDelete, setCanDelete] = useState(false);
|
const [canDelete, setCanDelete] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchKeys() {
|
async function fetchKeys() {
|
||||||
|
@ -337,11 +350,12 @@ function DeleteWorkspace({ workspace, onClick }) {
|
||||||
if (!canDelete) return null;
|
if (!canDelete) return null;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
disabled={deleting}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type="button"
|
type="button"
|
||||||
className="transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-transparent text-white hover:text-white hover:bg-red-600"
|
className="transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-transparent text-white hover:text-white hover:bg-red-600 disabled:bg-red-600 disabled:text-red-200 disabled:animate-pulse"
|
||||||
>
|
>
|
||||||
Delete Workspace
|
{deleting ? "Deleting Workspace..." : "Delete Workspace"}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
export default function AstraDBOptions({ settings }) {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
|
<div className="w-full flex items-center gap-4">
|
||||||
|
<div className="flex flex-col w-60">
|
||||||
|
<label className="text-white text-sm font-semibold block mb-4">
|
||||||
|
Astra DB Endpoint
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
name="AstraDBEndpoint"
|
||||||
|
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||||
|
placeholder="Astra DB API endpoint"
|
||||||
|
defaultValue={settings?.AstraDBEndpoint}
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col w-60">
|
||||||
|
<label className="text-white text-sm font-semibold block mb-4">
|
||||||
|
Astra DB Application Token
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="AstraDBApplicationToken"
|
||||||
|
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
|
||||||
|
placeholder="AstraCS:..."
|
||||||
|
defaultValue={
|
||||||
|
settings?.AstraDBApplicationToken ? "*".repeat(20) : ""
|
||||||
|
}
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -52,6 +52,29 @@ const Embed = {
|
||||||
return { success: true, error: e.message };
|
return { success: true, error: e.message };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
chats: async (offset = 0) => {
|
||||||
|
return await fetch(`${API_BASE}/embed/chats`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: baseHeaders(),
|
||||||
|
body: JSON.stringify({ offset }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteChat: async (chatId) => {
|
||||||
|
return await fetch(`${API_BASE}/embed/chats/${chatId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: baseHeaders(),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
return { success: false, error: e.message };
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Embed;
|
export default Embed;
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { useRef } from "react";
|
||||||
|
import truncate from "truncate";
|
||||||
|
import { X, Trash, LinkSimple } from "@phosphor-icons/react";
|
||||||
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
|
import { useModal } from "@/hooks/useModal";
|
||||||
|
import paths from "@/utils/paths";
|
||||||
|
import Embed from "@/models/embed";
|
||||||
|
|
||||||
|
export default function ChatRow({ chat }) {
|
||||||
|
const rowRef = useRef(null);
|
||||||
|
const {
|
||||||
|
isOpen: isPromptOpen,
|
||||||
|
openModal: openPromptModal,
|
||||||
|
closeModal: closePromptModal,
|
||||||
|
} = useModal();
|
||||||
|
const {
|
||||||
|
isOpen: isResponseOpen,
|
||||||
|
openModal: openResponseModal,
|
||||||
|
closeModal: closeResponseModal,
|
||||||
|
} = useModal();
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
`Are you sure you want to delete this chat?\n\nThis action is irreversible.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
rowRef?.current?.remove();
|
||||||
|
await Embed.deleteChat(chat.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<tr
|
||||||
|
ref={rowRef}
|
||||||
|
className="bg-transparent text-white text-opacity-80 text-sm font-medium"
|
||||||
|
>
|
||||||
|
<td className="px-6 py-4 font-medium whitespace-nowrap text-white">
|
||||||
|
<a
|
||||||
|
href={paths.settings.embedSetup()}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-white flex items-center hover:underline"
|
||||||
|
>
|
||||||
|
<LinkSimple className="mr-2 w-5 h-5" />{" "}
|
||||||
|
{chat.embed_config.workspace.name}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 font-medium whitespace-nowrap text-white">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p>{truncate(chat.session_id, 20)}</p>
|
||||||
|
<ConnectionDetails
|
||||||
|
connection_information={chat.connection_information}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
onClick={openPromptModal}
|
||||||
|
className="px-6 py-4 border-transparent cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
{truncate(chat.prompt, 40)}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
onClick={openResponseModal}
|
||||||
|
className="px-6 py-4 cursor-pointer transform transition-transform duration-200 hover:scale-105 hover:shadow-lg"
|
||||||
|
>
|
||||||
|
{truncate(JSON.parse(chat.response)?.text, 40)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4">{chat.createdAt}</td>
|
||||||
|
<td className="px-6 py-4 flex items-center gap-x-6">
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
className="font-medium text-red-300 px-2 py-1 rounded-lg hover:bg-red-800 hover:bg-opacity-20"
|
||||||
|
>
|
||||||
|
<Trash className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<ModalWrapper isOpen={isPromptOpen}>
|
||||||
|
<TextPreview text={chat.prompt} closeModal={closePromptModal} />
|
||||||
|
</ModalWrapper>
|
||||||
|
<ModalWrapper isOpen={isResponseOpen}>
|
||||||
|
<TextPreview
|
||||||
|
text={JSON.parse(chat.response)?.text}
|
||||||
|
closeModal={closeResponseModal}
|
||||||
|
/>
|
||||||
|
</ModalWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextPreview = ({ text, closeModal }) => {
|
||||||
|
return (
|
||||||
|
<div className="relative w-full md:max-w-2xl max-h-full">
|
||||||
|
<div className="relative bg-main-gradient rounded-lg shadow">
|
||||||
|
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-600">
|
||||||
|
<h3 className="text-xl font-semibold text-white">Viewing Text</h3>
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
type="button"
|
||||||
|
className="transition-all duration-300 text-gray-400 bg-transparent hover:border-white/60 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
|
||||||
|
>
|
||||||
|
<X className="text-gray-300 text-lg" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full p-6">
|
||||||
|
<pre className="w-full h-[200px] py-2 px-4 whitespace-pre-line overflow-auto rounded-lg bg-zinc-900 border border-gray-500 text-white text-sm">
|
||||||
|
{text}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConnectionDetails = ({ connection_information }) => {
|
||||||
|
let details = {};
|
||||||
|
try {
|
||||||
|
details = JSON.parse(connection_information);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (Object.keys(details).length === 0) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{details.ip && <p className="text-xs text-slate-400">{details.ip}</p>}
|
||||||
|
{details.host && <p className="text-xs text-slate-400">{details.host}</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Sidebar, { SidebarMobileHeader } from "@/components/SettingsSidebar";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
|
import * as Skeleton from "react-loading-skeleton";
|
||||||
|
import "react-loading-skeleton/dist/skeleton.css";
|
||||||
|
import useQuery from "@/hooks/useQuery";
|
||||||
|
import ChatRow from "./ChatRow";
|
||||||
|
import Embed from "@/models/embed";
|
||||||
|
|
||||||
|
export default function EmbedChats() {
|
||||||
|
// TODO [FEAT]: Add export of embed chats
|
||||||
|
return (
|
||||||
|
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||||
|
{!isMobile && <Sidebar />}
|
||||||
|
<div
|
||||||
|
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||||
|
className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[26px] bg-main-gradient w-full h-full overflow-y-scroll border-4 border-accent"
|
||||||
|
>
|
||||||
|
{isMobile && <SidebarMobileHeader />}
|
||||||
|
<div className="flex flex-col w-full px-1 md:px-20 md:py-12 py-16">
|
||||||
|
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||||
|
<div className="items-center flex gap-x-4">
|
||||||
|
<p className="text-2xl font-semibold text-white">Embed Chats</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-base text-white text-opacity-60">
|
||||||
|
These are all the recorded chats and messages from any embed that
|
||||||
|
you have published.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ChatsContainer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatsContainer() {
|
||||||
|
const query = useQuery();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [chats, setChats] = useState([]);
|
||||||
|
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
||||||
|
const [canNext, setCanNext] = useState(false);
|
||||||
|
|
||||||
|
const handlePrevious = () => {
|
||||||
|
setOffset(Math.max(offset - 1, 0));
|
||||||
|
};
|
||||||
|
const handleNext = () => {
|
||||||
|
setOffset(offset + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchChats() {
|
||||||
|
const { chats: _chats, hasPages = false } = await Embed.chats(offset);
|
||||||
|
setChats(_chats);
|
||||||
|
setCanNext(hasPages);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
fetchChats();
|
||||||
|
}, [offset]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Skeleton.default
|
||||||
|
height="80vh"
|
||||||
|
width="100%"
|
||||||
|
highlightColor="#3D4147"
|
||||||
|
baseColor="#2C2F35"
|
||||||
|
count={1}
|
||||||
|
className="w-full p-4 rounded-b-2xl rounded-tr-2xl rounded-tl-sm mt-6"
|
||||||
|
containerClassName="flex w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<table className="w-full text-sm text-left rounded-lg mt-5">
|
||||||
|
<thead className="text-white text-opacity-80 text-sm font-bold uppercase border-white border-b border-opacity-60">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||||
|
Embed
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Sender
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Message
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Response
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Sent At
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||||
|
{" "}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{!!chats &&
|
||||||
|
chats.map((chat) => <ChatRow key={chat.id} chat={chat} />)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="flex w-full justify-between items-center">
|
||||||
|
<button
|
||||||
|
onClick={handlePrevious}
|
||||||
|
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible"
|
||||||
|
disabled={offset === 0}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
Previous Page
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible"
|
||||||
|
disabled={!canNext}
|
||||||
|
>
|
||||||
|
Next Page
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,11 +2,7 @@ import React, { useState } from "react";
|
||||||
import { CheckCircle, CopySimple, X } from "@phosphor-icons/react";
|
import { CheckCircle, CopySimple, X } from "@phosphor-icons/react";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
import { encode as HTMLEncode } from "he";
|
|
||||||
|
|
||||||
// import hljsHTML from 'highlight.js/lib/languages/vbscript-html';
|
|
||||||
import "highlight.js/styles/github-dark-dimmed.min.css";
|
import "highlight.js/styles/github-dark-dimmed.min.css";
|
||||||
// hljs.registerLanguage('html', hljsHTML)
|
|
||||||
|
|
||||||
export default function CodeSnippetModal({ embed, closeModal }) {
|
export default function CodeSnippetModal({ embed, closeModal }) {
|
||||||
return (
|
return (
|
||||||
|
@ -51,7 +47,7 @@ function createScriptTagSnippet(embed, scriptHost, serverHost) {
|
||||||
return `<!--
|
return `<!--
|
||||||
Paste this script at the bottom of your HTML before the </body> tag.
|
Paste this script at the bottom of your HTML before the </body> tag.
|
||||||
See more style and config options on our docs
|
See more style and config options on our docs
|
||||||
https://docs.useanything.com/feature-overview/embed
|
https://github.com/Mintplex-Labs/anything-llm/tree/master/embed/README.md
|
||||||
-->
|
-->
|
||||||
<script
|
<script
|
||||||
data-embed-id="${embed.uuid}"
|
data-embed-id="${embed.uuid}"
|
||||||
|
|
|
@ -130,7 +130,7 @@ export const WorkspaceSelection = ({ defaultValue = null }) => {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col mb-2">
|
<div className="flex flex-col mb-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="workspaceId"
|
htmlFor="workspace_id"
|
||||||
className="block text-sm font-medium text-white"
|
className="block text-sm font-medium text-white"
|
||||||
>
|
>
|
||||||
Workspace
|
Workspace
|
||||||
|
@ -141,13 +141,20 @@ export const WorkspaceSelection = ({ defaultValue = null }) => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
name="workspaceId"
|
name="workspace_id"
|
||||||
required={true}
|
required={true}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
className="min-w-[15rem] rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
className="min-w-[15rem] rounded-lg bg-zinc-900 px-4 py-2 text-sm text-white border border-gray-500 focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
{workspaces.map((workspace) => {
|
{workspaces.map((workspace) => {
|
||||||
return <option value={workspace.id}>{workspace.name}</option>;
|
return (
|
||||||
|
<option
|
||||||
|
selected={defaultValue === workspace.id}
|
||||||
|
value={workspace.id}
|
||||||
|
>
|
||||||
|
{workspace.name}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
||||||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||||
|
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||||
import PreLoader from "@/components/Preloader";
|
import PreLoader from "@/components/Preloader";
|
||||||
import ChangeWarningModal from "@/components/ChangeWarning";
|
import ChangeWarningModal from "@/components/ChangeWarning";
|
||||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||||
|
@ -23,6 +24,7 @@ import MilvusDBOptions from "@/components/VectorDBSelection/MilvusDBOptions";
|
||||||
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
||||||
import { useModal } from "@/hooks/useModal";
|
import { useModal } from "@/hooks/useModal";
|
||||||
import ModalWrapper from "@/components/ModalWrapper";
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
|
import AstraDBOptions from "@/components/VectorDBSelection/AstraDBOptions";
|
||||||
|
|
||||||
export default function GeneralVectorDatabase() {
|
export default function GeneralVectorDatabase() {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
@ -100,6 +102,13 @@ export default function GeneralVectorDatabase() {
|
||||||
options: <MilvusDBOptions settings={settings} />,
|
options: <MilvusDBOptions settings={settings} />,
|
||||||
description: "Open-source, highly scalable, and blazing fast.",
|
description: "Open-source, highly scalable, and blazing fast.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "AstraDB",
|
||||||
|
value: "astra",
|
||||||
|
logo: AstraDBLogo,
|
||||||
|
options: <AstraDBOptions settings={settings} />,
|
||||||
|
description: "Vector Search for Real-world GenAI.",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const updateVectorChoice = (selection) => {
|
const updateVectorChoice = (selection) => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
||||||
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||||
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
||||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||||
|
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||||
import ChromaLogo from "@/media/vectordbs/chroma.png";
|
import ChromaLogo from "@/media/vectordbs/chroma.png";
|
||||||
import PineconeLogo from "@/media/vectordbs/pinecone.png";
|
import PineconeLogo from "@/media/vectordbs/pinecone.png";
|
||||||
import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
||||||
|
@ -147,6 +148,13 @@ const VECTOR_DB_PRIVACY = {
|
||||||
],
|
],
|
||||||
logo: ZillizLogo,
|
logo: ZillizLogo,
|
||||||
},
|
},
|
||||||
|
astra: {
|
||||||
|
name: "AstraDB",
|
||||||
|
description: [
|
||||||
|
"Your vectors and document text are stored on your cloud AstraDB database.",
|
||||||
|
],
|
||||||
|
logo: AstraDBLogo,
|
||||||
|
},
|
||||||
lancedb: {
|
lancedb: {
|
||||||
name: "LanceDB",
|
name: "LanceDB",
|
||||||
description: [
|
description: [
|
||||||
|
|
|
@ -7,6 +7,7 @@ import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
||||||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||||
|
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||||
import System from "@/models/system";
|
import System from "@/models/system";
|
||||||
import paths from "@/utils/paths";
|
import paths from "@/utils/paths";
|
||||||
import PineconeDBOptions from "@/components/VectorDBSelection/PineconeDBOptions";
|
import PineconeDBOptions from "@/components/VectorDBSelection/PineconeDBOptions";
|
||||||
|
@ -16,6 +17,7 @@ import WeaviateDBOptions from "@/components/VectorDBSelection/WeaviateDBOptions"
|
||||||
import LanceDBOptions from "@/components/VectorDBSelection/LanceDBOptions";
|
import LanceDBOptions from "@/components/VectorDBSelection/LanceDBOptions";
|
||||||
import MilvusOptions from "@/components/VectorDBSelection/MilvusDBOptions";
|
import MilvusOptions from "@/components/VectorDBSelection/MilvusDBOptions";
|
||||||
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
||||||
|
import AstraDBOptions from "@/components/VectorDBSelection/AstraDBOptions";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import VectorDBItem from "@/components/VectorDBSelection/VectorDBItem";
|
import VectorDBItem from "@/components/VectorDBSelection/VectorDBItem";
|
||||||
|
@ -100,6 +102,13 @@ export default function VectorDatabaseConnection({
|
||||||
options: <MilvusOptions settings={settings} />,
|
options: <MilvusOptions settings={settings} />,
|
||||||
description: "Open-source, highly scalable, and blazing fast.",
|
description: "Open-source, highly scalable, and blazing fast.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "AstraDB",
|
||||||
|
value: "astra",
|
||||||
|
logo: AstraDBLogo,
|
||||||
|
options: <AstraDBOptions settings={settings} />,
|
||||||
|
description: "Vector Search for Real-world GenAI.",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function handleForward() {
|
function handleForward() {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"prisma:migrate": "cd server && npx prisma migrate dev --name init",
|
"prisma:migrate": "cd server && npx prisma migrate dev --name init",
|
||||||
"prisma:seed": "cd server && npx prisma db seed",
|
"prisma:seed": "cd server && npx prisma db seed",
|
||||||
"prisma:setup": "yarn prisma:generate && yarn prisma:migrate && yarn prisma:seed",
|
"prisma:setup": "yarn prisma:generate && yarn prisma:migrate && yarn prisma:seed",
|
||||||
"prisma:reset": "cd server && npx prisma db push --force-reset",
|
"prisma:reset": "truncate -s 0 server/storage/anythingllm.db && yarn prisma:migrate",
|
||||||
"prod:server": "cd server && yarn start",
|
"prod:server": "cd server && yarn start",
|
||||||
"prod:frontend": "cd frontend && yarn build",
|
"prod:frontend": "cd frontend && yarn build",
|
||||||
"generate:cloudformation": "node cloud-deployments/aws/cloudformation/generate.mjs",
|
"generate:cloudformation": "node cloud-deployments/aws/cloudformation/generate.mjs",
|
||||||
|
|
|
@ -51,6 +51,7 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
|
||||||
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
|
# Only used if you are using an LLM that does not natively support embedding (openai or Azure)
|
||||||
# EMBEDDING_ENGINE='openai'
|
# EMBEDDING_ENGINE='openai'
|
||||||
# OPEN_AI_KEY=sk-xxxx
|
# OPEN_AI_KEY=sk-xxxx
|
||||||
|
# EMBEDDING_MODEL_PREF='text-embedding-ada-002'
|
||||||
|
|
||||||
# EMBEDDING_ENGINE='azure'
|
# EMBEDDING_ENGINE='azure'
|
||||||
# AZURE_OPENAI_ENDPOINT=
|
# AZURE_OPENAI_ENDPOINT=
|
||||||
|
@ -76,6 +77,11 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
|
||||||
# PINECONE_API_KEY=
|
# PINECONE_API_KEY=
|
||||||
# PINECONE_INDEX=
|
# PINECONE_INDEX=
|
||||||
|
|
||||||
|
# Enable all below if you are using vector database: Astra DB.
|
||||||
|
# VECTOR_DB="astra"
|
||||||
|
# ASTRA_DB_APPLICATION_TOKEN=
|
||||||
|
# ASTRA_DB_ENDPOINT=
|
||||||
|
|
||||||
# Enable all below if you are using vector database: LanceDB.
|
# Enable all below if you are using vector database: LanceDB.
|
||||||
VECTOR_DB="lancedb"
|
VECTOR_DB="lancedb"
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ storage/imports
|
||||||
!storage/documents/DOCUMENTS.md
|
!storage/documents/DOCUMENTS.md
|
||||||
logs/server.log
|
logs/server.log
|
||||||
*.db
|
*.db
|
||||||
|
*.db-journal
|
||||||
storage/lancedb
|
storage/lancedb
|
||||||
public/
|
public/
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ function chatEndpoints(app) {
|
||||||
async (request, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const user = await userFromSession(request, response);
|
const user = await userFromSession(request, response);
|
||||||
// console.log("user", user);
|
|
||||||
const { slug } = request.params;
|
const { slug } = request.params;
|
||||||
const { message, mode = "query" } = reqBody(request);
|
const { message, mode = "query" } = reqBody(request);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
const { EmbedChats } = require("../models/embedChats");
|
const { EmbedChats } = require("../models/embedChats");
|
||||||
const { EmbedConfig } = require("../models/embedConfig");
|
const { EmbedConfig } = require("../models/embedConfig");
|
||||||
const { reqBody, userFromSession } = require("../utils/http");
|
const { reqBody, userFromSession } = require("../utils/http");
|
||||||
const {
|
const { validEmbedConfigId } = require("../utils/middleware/embedMiddleware");
|
||||||
validEmbedConfig,
|
|
||||||
validEmbedConfigId,
|
|
||||||
} = require("../utils/middleware/embedMiddleware");
|
|
||||||
const {
|
const {
|
||||||
flexUserRoleValid,
|
flexUserRoleValid,
|
||||||
ROLES,
|
ROLES,
|
||||||
|
@ -80,14 +77,14 @@ function embedManagementEndpoints(app) {
|
||||||
app.post(
|
app.post(
|
||||||
"/embed/chats",
|
"/embed/chats",
|
||||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||||
async (_, response) => {
|
async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const { offset = 0, limit = 20 } = reqBody(request);
|
const { offset = 0, limit = 20 } = reqBody(request);
|
||||||
const embedChats = await EmbedChats.whereWithEmbed(
|
const embedChats = await EmbedChats.whereWithEmbedAndWorkspace(
|
||||||
{},
|
{},
|
||||||
limit,
|
limit,
|
||||||
offset * limit,
|
{ id: "desc" },
|
||||||
{ id: "desc" }
|
offset * limit
|
||||||
);
|
);
|
||||||
const totalChats = await EmbedChats.count();
|
const totalChats = await EmbedChats.count();
|
||||||
const hasPages = totalChats > (offset + 1) * limit;
|
const hasPages = totalChats > (offset + 1) * limit;
|
||||||
|
@ -98,6 +95,21 @@ function embedManagementEndpoints(app) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.delete(
|
||||||
|
"/embed/chats/:chatId",
|
||||||
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||||
|
async (request, response) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = request.params;
|
||||||
|
await EmbedChats.delete({ id: Number(chatId) });
|
||||||
|
response.status(200).json({ success: true, error: null });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
response.sendStatus(500).end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { embedManagementEndpoints };
|
module.exports = { embedManagementEndpoints };
|
||||||
|
|
|
@ -106,6 +106,9 @@ const Document = {
|
||||||
await prisma.workspace_documents.delete({
|
await prisma.workspace_documents.delete({
|
||||||
where: { id: document.id, workspaceId: workspace.id },
|
where: { id: document.id, workspaceId: workspace.id },
|
||||||
});
|
});
|
||||||
|
await prisma.document_vectors.deleteMany({
|
||||||
|
where: { docId: document.docId },
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ const EmbedChats = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
whereWithEmbed: async function (
|
whereWithEmbedAndWorkspace: async function (
|
||||||
clause = {},
|
clause = {},
|
||||||
limit = null,
|
limit = null,
|
||||||
orderBy = null,
|
orderBy = null,
|
||||||
|
@ -124,7 +124,17 @@ const EmbedChats = {
|
||||||
try {
|
try {
|
||||||
const chats = await prisma.embed_chats.findMany({
|
const chats = await prisma.embed_chats.findMany({
|
||||||
where: clause,
|
where: clause,
|
||||||
include: { embed_config: true },
|
include: {
|
||||||
|
embed_config: {
|
||||||
|
select: {
|
||||||
|
workspace: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
...(limit !== null ? { take: limit } : {}),
|
...(limit !== null ? { take: limit } : {}),
|
||||||
...(offset !== null ? { skip: offset } : {}),
|
...(offset !== null ? { skip: offset } : {}),
|
||||||
...(orderBy !== null ? { orderBy } : {}),
|
...(orderBy !== null ? { orderBy } : {}),
|
||||||
|
|
|
@ -13,6 +13,7 @@ const EmbedConfig = {
|
||||||
"max_chats_per_day",
|
"max_chats_per_day",
|
||||||
"max_chats_per_session",
|
"max_chats_per_session",
|
||||||
"chat_mode",
|
"chat_mode",
|
||||||
|
"workspace_id",
|
||||||
],
|
],
|
||||||
|
|
||||||
new: async function (data, creatorId = null) {
|
new: async function (data, creatorId = null) {
|
||||||
|
@ -48,7 +49,7 @@ const EmbedConfig = {
|
||||||
),
|
),
|
||||||
createdBy: Number(creatorId) ?? null,
|
createdBy: Number(creatorId) ?? null,
|
||||||
workspace: {
|
workspace: {
|
||||||
connect: { id: Number(data.workspaceId) },
|
connect: { id: Number(data.workspace_id) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -185,7 +186,11 @@ const BOOLEAN_KEYS = [
|
||||||
"enabled",
|
"enabled",
|
||||||
];
|
];
|
||||||
|
|
||||||
const NUMBER_KEYS = ["max_chats_per_day", "max_chats_per_session"];
|
const NUMBER_KEYS = [
|
||||||
|
"max_chats_per_day",
|
||||||
|
"max_chats_per_session",
|
||||||
|
"workspace_id",
|
||||||
|
];
|
||||||
|
|
||||||
// Helper to validate a data object strictly into the proper format
|
// Helper to validate a data object strictly into the proper format
|
||||||
function validatedCreationData(value, field) {
|
function validatedCreationData(value, field) {
|
||||||
|
|
|
@ -68,6 +68,12 @@ const SystemSettings = {
|
||||||
ZillizApiToken: process.env.ZILLIZ_API_TOKEN,
|
ZillizApiToken: process.env.ZILLIZ_API_TOKEN,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
...(vectorDB === "astra"
|
||||||
|
? {
|
||||||
|
AstraDBApplicationToken: process?.env?.ASTRA_DB_APPLICATION_TOKEN,
|
||||||
|
AstraDBEndpoint: process?.env?.ASTRA_DB_ENDPOINT,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
LLMProvider: llmProvider,
|
LLMProvider: llmProvider,
|
||||||
...(llmProvider === "openai"
|
...(llmProvider === "openai"
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -3,6 +3,7 @@ const slugify = require("slugify");
|
||||||
const { Document } = require("./documents");
|
const { Document } = require("./documents");
|
||||||
const { WorkspaceUser } = require("./workspaceUsers");
|
const { WorkspaceUser } = require("./workspaceUsers");
|
||||||
const { ROLES } = require("../utils/middleware/multiUserProtected");
|
const { ROLES } = require("../utils/middleware/multiUserProtected");
|
||||||
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
|
||||||
const Workspace = {
|
const Workspace = {
|
||||||
writable: [
|
writable: [
|
||||||
|
@ -22,6 +23,7 @@ const Workspace = {
|
||||||
new: async function (name = null, creatorId = null) {
|
new: async function (name = null, creatorId = null) {
|
||||||
if (!name) return { result: null, message: "name cannot be null" };
|
if (!name) return { result: null, message: "name cannot be null" };
|
||||||
var slug = slugify(name, { lower: true });
|
var slug = slugify(name, { lower: true });
|
||||||
|
slug = slug || uuidv4();
|
||||||
|
|
||||||
const existingBySlug = await this.get({ slug });
|
const existingBySlug = await this.get({ slug });
|
||||||
if (existingBySlug !== null) {
|
if (existingBySlug !== null) {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.8.1",
|
"@anthropic-ai/sdk": "^0.8.1",
|
||||||
"@azure/openai": "1.0.0-beta.10",
|
"@azure/openai": "1.0.0-beta.10",
|
||||||
|
"@datastax/astra-db-ts": "^0.1.3",
|
||||||
"@google/generative-ai": "^0.1.3",
|
"@google/generative-ai": "^0.1.3",
|
||||||
"@googleapis/youtube": "^9.0.0",
|
"@googleapis/youtube": "^9.0.0",
|
||||||
"@pinecone-database/pinecone": "^2.0.1",
|
"@pinecone-database/pinecone": "^2.0.1",
|
||||||
|
|
|
@ -52,6 +52,8 @@ class OpenAiLLM {
|
||||||
return 8192;
|
return 8192;
|
||||||
case "gpt-4-1106-preview":
|
case "gpt-4-1106-preview":
|
||||||
return 128000;
|
return 128000;
|
||||||
|
case "gpt-4-turbo-preview":
|
||||||
|
return 128000;
|
||||||
case "gpt-4-32k":
|
case "gpt-4-32k":
|
||||||
return 32000;
|
return 32000;
|
||||||
default:
|
default:
|
||||||
|
@ -65,6 +67,7 @@ class OpenAiLLM {
|
||||||
"gpt-3.5-turbo",
|
"gpt-3.5-turbo",
|
||||||
"gpt-3.5-turbo-1106",
|
"gpt-3.5-turbo-1106",
|
||||||
"gpt-4-1106-preview",
|
"gpt-4-1106-preview",
|
||||||
|
"gpt-4-turbo-preview",
|
||||||
"gpt-4-32k",
|
"gpt-4-32k",
|
||||||
];
|
];
|
||||||
const isPreset = validModels.some((model) => modelName === model);
|
const isPreset = validModels.some((model) => modelName === model);
|
||||||
|
|
|
@ -9,6 +9,7 @@ class OpenAiEmbedder {
|
||||||
});
|
});
|
||||||
const openai = new OpenAIApi(config);
|
const openai = new OpenAIApi(config);
|
||||||
this.openai = openai;
|
this.openai = openai;
|
||||||
|
this.model = process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002";
|
||||||
|
|
||||||
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
||||||
this.maxConcurrentChunks = 500;
|
this.maxConcurrentChunks = 500;
|
||||||
|
@ -30,7 +31,7 @@ class OpenAiEmbedder {
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
this.openai
|
this.openai
|
||||||
.createEmbedding({
|
.createEmbedding({
|
||||||
model: "text-embedding-ada-002",
|
model: this.model,
|
||||||
input: chunk,
|
input: chunk,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
|
|
@ -50,7 +50,7 @@ async function streamChatWithForEmbed(
|
||||||
id: uuid,
|
id: uuid,
|
||||||
type: "textResponse",
|
type: "textResponse",
|
||||||
textResponse:
|
textResponse:
|
||||||
"There is no relevant information in this workspace to answer your query.",
|
"I do not have enough information to answer that. Try another question.",
|
||||||
sources: [],
|
sources: [],
|
||||||
close: true,
|
close: true,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
|
@ -269,6 +269,7 @@ function handleStreamResponses(response, stream, responseProps) {
|
||||||
for (const choice of event.choices) {
|
for (const choice of event.choices) {
|
||||||
const delta = choice.delta?.content;
|
const delta = choice.delta?.content;
|
||||||
if (!delta) continue;
|
if (!delta) continue;
|
||||||
|
fullText += delta;
|
||||||
writeResponseChunk(response, {
|
writeResponseChunk(response, {
|
||||||
uuid,
|
uuid,
|
||||||
sources: [],
|
sources: [],
|
||||||
|
|
|
@ -22,6 +22,9 @@ function getVectorDbClass() {
|
||||||
case "zilliz":
|
case "zilliz":
|
||||||
const { Zilliz } = require("../vectorDbProviders/zilliz");
|
const { Zilliz } = require("../vectorDbProviders/zilliz");
|
||||||
return Zilliz;
|
return Zilliz;
|
||||||
|
case "astra":
|
||||||
|
const { AstraDB } = require("../vectorDbProviders/astra");
|
||||||
|
return AstraDB;
|
||||||
default:
|
default:
|
||||||
throw new Error("ENV: No VECTOR_DB value found in environment!");
|
throw new Error("ENV: No VECTOR_DB value found in environment!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,6 +204,17 @@ const KEY_MAPPING = {
|
||||||
checks: [isNotEmpty],
|
checks: [isNotEmpty],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Astra DB Options
|
||||||
|
|
||||||
|
AstraDBApplicationToken: {
|
||||||
|
envKey: "ASTRA_DB_APPLICATION_TOKEN",
|
||||||
|
checks: [isNotEmpty],
|
||||||
|
},
|
||||||
|
AstraDBEndpoint: {
|
||||||
|
envKey: "ASTRA_DB_ENDPOINT",
|
||||||
|
checks: [isNotEmpty],
|
||||||
|
},
|
||||||
|
|
||||||
// Together Ai Options
|
// Together Ai Options
|
||||||
TogetherAiApiKey: {
|
TogetherAiApiKey: {
|
||||||
envKey: "TOGETHER_AI_API_KEY",
|
envKey: "TOGETHER_AI_API_KEY",
|
||||||
|
@ -322,6 +333,7 @@ function supportedVectorDB(input = "") {
|
||||||
"qdrant",
|
"qdrant",
|
||||||
"milvus",
|
"milvus",
|
||||||
"zilliz",
|
"zilliz",
|
||||||
|
"astra",
|
||||||
];
|
];
|
||||||
return supported.includes(input)
|
return supported.includes(input)
|
||||||
? null
|
? null
|
||||||
|
|
|
@ -21,9 +21,8 @@ async function validEmbedConfig(request, response, next) {
|
||||||
|
|
||||||
function setConnectionMeta(request, response, next) {
|
function setConnectionMeta(request, response, next) {
|
||||||
response.locals.connection = {
|
response.locals.connection = {
|
||||||
host: request.hostname,
|
host: request.headers?.origin,
|
||||||
path: request.path,
|
ip: request?.ip,
|
||||||
ip: request.ip,
|
|
||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# How to setup Astra Vector Database for AnythingLLM
|
||||||
|
|
||||||
|
[Official Astra DB Docs](https://docs.datastax.com/en/astra/astra-db-vector/get-started/quickstart.html) for reference.
|
||||||
|
|
||||||
|
### How to get started
|
||||||
|
|
||||||
|
**Requirements**
|
||||||
|
|
||||||
|
- Astra Vector Database with active status.
|
||||||
|
|
||||||
|
**Instructions**
|
||||||
|
|
||||||
|
- [Create an Astra account or sign in to an existing Astra account](astra.datastax.com)
|
||||||
|
- Create an Astra Serverless(Vector) Database.
|
||||||
|
- Make sure DB is in active state.
|
||||||
|
- Get `API ENDPOINT`and `Application Token` from Overview screen
|
||||||
|
|
||||||
|
```
|
||||||
|
VECTOR_DB="astra"
|
||||||
|
ASTRA_DB_ENDPOINT=Astra DB API endpoint
|
||||||
|
ASTRA_DB_APPLICATION_TOKEN=AstraCS:..
|
||||||
|
```
|
|
@ -0,0 +1,380 @@
|
||||||
|
const { AstraDB: AstraClient } = require("@datastax/astra-db-ts");
|
||||||
|
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||||
|
const { storeVectorResult, cachedVectorInformation } = require("../../files");
|
||||||
|
const { v4: uuidv4 } = require("uuid");
|
||||||
|
const {
|
||||||
|
toChunks,
|
||||||
|
getLLMProvider,
|
||||||
|
getEmbeddingEngineSelection,
|
||||||
|
} = require("../../helpers");
|
||||||
|
|
||||||
|
const AstraDB = {
|
||||||
|
name: "AstraDB",
|
||||||
|
connect: async function () {
|
||||||
|
if (process.env.VECTOR_DB !== "astra")
|
||||||
|
throw new Error("AstraDB::Invalid ENV settings");
|
||||||
|
|
||||||
|
const client = new AstraClient(
|
||||||
|
process?.env?.ASTRA_DB_APPLICATION_TOKEN,
|
||||||
|
process?.env?.ASTRA_DB_ENDPOINT
|
||||||
|
);
|
||||||
|
return { client };
|
||||||
|
},
|
||||||
|
heartbeat: async function () {
|
||||||
|
return { heartbeat: Number(new Date()) };
|
||||||
|
},
|
||||||
|
// Astra interface will return a valid collection object even if the collection
|
||||||
|
// does not actually exist. So we run a simple check which will always throw
|
||||||
|
// when the table truly does not exist. Faster than iterating all collections.
|
||||||
|
isRealCollection: async function (astraCollection = null) {
|
||||||
|
if (!astraCollection) return false;
|
||||||
|
return await astraCollection
|
||||||
|
.countDocuments()
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
totalVectors: async function () {
|
||||||
|
const { client } = await this.connect();
|
||||||
|
const collectionNames = await this.allNamespaces(client);
|
||||||
|
var totalVectors = 0;
|
||||||
|
for (const name of collectionNames) {
|
||||||
|
const collection = await client.collection(name).catch(() => null);
|
||||||
|
const count = await collection.countDocuments().catch(() => 0);
|
||||||
|
totalVectors += count ? count : 0;
|
||||||
|
}
|
||||||
|
return totalVectors;
|
||||||
|
},
|
||||||
|
namespaceCount: async function (_namespace = null) {
|
||||||
|
const { client } = await this.connect();
|
||||||
|
const namespace = await this.namespace(client, _namespace);
|
||||||
|
return namespace?.vectorCount || 0;
|
||||||
|
},
|
||||||
|
namespace: async function (client, namespace = null) {
|
||||||
|
if (!namespace) throw new Error("No namespace value provided.");
|
||||||
|
const collection = await client.collection(namespace).catch(() => null);
|
||||||
|
if (!(await this.isRealCollection(collection))) return null;
|
||||||
|
|
||||||
|
const count = await collection.countDocuments().catch((e) => {
|
||||||
|
console.error("Astra::namespaceExists", e.message);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: namespace,
|
||||||
|
...collection,
|
||||||
|
vectorCount: typeof count === "number" ? count : 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
hasNamespace: async function (namespace = null) {
|
||||||
|
if (!namespace) return false;
|
||||||
|
const { client } = await this.connect();
|
||||||
|
return await this.namespaceExists(client, namespace);
|
||||||
|
},
|
||||||
|
namespaceExists: async function (client, namespace = null) {
|
||||||
|
if (!namespace) throw new Error("No namespace value provided.");
|
||||||
|
const collection = await client.collection(namespace);
|
||||||
|
return await this.isRealCollection(collection);
|
||||||
|
},
|
||||||
|
deleteVectorsInNamespace: async function (client, namespace = null) {
|
||||||
|
await client.dropCollection(namespace);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
// AstraDB requires a dimension aspect for collection creation
|
||||||
|
// we pass this in from the first chunk to infer the dimensions like other
|
||||||
|
// providers do.
|
||||||
|
getOrCreateCollection: async function (client, namespace, dimensions = null) {
|
||||||
|
const isExists = await this.namespaceExists(client, namespace);
|
||||||
|
if (!isExists) {
|
||||||
|
if (!dimensions)
|
||||||
|
throw new Error(
|
||||||
|
`AstraDB:getOrCreateCollection Unable to infer vector dimension from input. Open an issue on Github for support.`
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.createCollection(namespace, {
|
||||||
|
vector: {
|
||||||
|
dimension: dimensions,
|
||||||
|
metric: "cosine",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await client.collection(namespace);
|
||||||
|
},
|
||||||
|
addDocumentToNamespace: async function (
|
||||||
|
namespace,
|
||||||
|
documentData = {},
|
||||||
|
fullFilePath = null
|
||||||
|
) {
|
||||||
|
const { DocumentVectors } = require("../../../models/vectors");
|
||||||
|
try {
|
||||||
|
let vectorDimension = null;
|
||||||
|
const { pageContent, docId, ...metadata } = documentData;
|
||||||
|
if (!pageContent || pageContent.length == 0) return false;
|
||||||
|
|
||||||
|
console.log("Adding new vectorized document into namespace", namespace);
|
||||||
|
const cacheResult = await cachedVectorInformation(fullFilePath);
|
||||||
|
if (cacheResult.exists) {
|
||||||
|
const { client } = await this.connect();
|
||||||
|
const { chunks } = cacheResult;
|
||||||
|
const documentVectors = [];
|
||||||
|
vectorDimension = chunks[0][0].values.length || null;
|
||||||
|
|
||||||
|
const collection = await this.getOrCreateCollection(
|
||||||
|
client,
|
||||||
|
namespace,
|
||||||
|
vectorDimension
|
||||||
|
);
|
||||||
|
if (!(await this.isRealCollection(collection)))
|
||||||
|
throw new Error("Failed to create new AstraDB collection!", {
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
// Before sending to Astra and saving the records to our db
|
||||||
|
// we need to assign the id of each chunk that is stored in the cached file.
|
||||||
|
const newChunks = chunk.map((chunk) => {
|
||||||
|
const _id = uuidv4();
|
||||||
|
documentVectors.push({ docId, vectorId: _id });
|
||||||
|
return {
|
||||||
|
_id: _id,
|
||||||
|
$vector: chunk.values,
|
||||||
|
metadata: chunk.metadata || {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await collection.insertMany(newChunks);
|
||||||
|
}
|
||||||
|
await DocumentVectors.bulkInsert(documentVectors);
|
||||||
|
return { vectorized: true, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSplitter = new RecursiveCharacterTextSplitter({
|
||||||
|
chunkSize:
|
||||||
|
getEmbeddingEngineSelection()?.embeddingMaxChunkLength || 1_000,
|
||||||
|
chunkOverlap: 20,
|
||||||
|
});
|
||||||
|
const textChunks = await textSplitter.splitText(pageContent);
|
||||||
|
|
||||||
|
console.log("Chunks created from document:", textChunks.length);
|
||||||
|
const LLMConnector = getLLMProvider();
|
||||||
|
const documentVectors = [];
|
||||||
|
const vectors = [];
|
||||||
|
const vectorValues = await LLMConnector.embedChunks(textChunks);
|
||||||
|
|
||||||
|
if (!!vectorValues && vectorValues.length > 0) {
|
||||||
|
for (const [i, vector] of vectorValues.entries()) {
|
||||||
|
if (!vectorDimension) vectorDimension = vector.length;
|
||||||
|
const vectorRecord = {
|
||||||
|
_id: uuidv4(),
|
||||||
|
$vector: vector,
|
||||||
|
metadata: { ...metadata, text: textChunks[i] },
|
||||||
|
};
|
||||||
|
|
||||||
|
vectors.push(vectorRecord);
|
||||||
|
documentVectors.push({ docId, vectorId: vectorRecord._id });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Could not embed document chunks! This document will not be recorded."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { client } = await this.connect();
|
||||||
|
const collection = await this.getOrCreateCollection(
|
||||||
|
client,
|
||||||
|
namespace,
|
||||||
|
vectorDimension
|
||||||
|
);
|
||||||
|
if (!(await this.isRealCollection(collection)))
|
||||||
|
throw new Error("Failed to create new AstraDB collection!", {
|
||||||
|
namespace,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (vectors.length > 0) {
|
||||||
|
const chunks = [];
|
||||||
|
|
||||||
|
console.log("Inserting vectorized chunks into Astra DB.");
|
||||||
|
|
||||||
|
// AstraDB has maximum upsert size of 20 records per-request so we have to use a lower chunk size here
|
||||||
|
// in order to do the queries - this takes a lot more time than other providers but there
|
||||||
|
// is no way around it. This will save the vector-cache with the same layout, so we don't
|
||||||
|
// have to chunk again for cached files.
|
||||||
|
for (const chunk of toChunks(vectors, 20)) {
|
||||||
|
chunks.push(
|
||||||
|
chunk.map((c) => {
|
||||||
|
return { id: c._id, values: c.$vector, metadata: c.metadata };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await collection.insertMany(chunk);
|
||||||
|
}
|
||||||
|
await storeVectorResult(chunks, fullFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await DocumentVectors.bulkInsert(documentVectors);
|
||||||
|
return { vectorized: true, error: null };
|
||||||
|
} catch (e) {
|
||||||
|
console.error("addDocumentToNamespace", e.message);
|
||||||
|
return { vectorized: false, error: e.message };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteDocumentFromNamespace: async function (namespace, docId) {
|
||||||
|
const { DocumentVectors } = require("../../../models/vectors");
|
||||||
|
const { client } = await this.connect();
|
||||||
|
if (!(await this.namespaceExists(client, namespace)))
|
||||||
|
throw new Error(
|
||||||
|
"Invalid namespace - has it been collected and populated yet?"
|
||||||
|
);
|
||||||
|
const collection = await client.collection(namespace);
|
||||||
|
|
||||||
|
const knownDocuments = await DocumentVectors.where({ docId });
|
||||||
|
if (knownDocuments.length === 0) return;
|
||||||
|
|
||||||
|
const vectorIds = knownDocuments.map((doc) => doc.vectorId);
|
||||||
|
for (const id of vectorIds) {
|
||||||
|
await collection.deleteMany({
|
||||||
|
_id: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexes = knownDocuments.map((doc) => doc.id);
|
||||||
|
await DocumentVectors.deleteIds(indexes);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
performSimilaritySearch: async function ({
|
||||||
|
namespace = null,
|
||||||
|
input = "",
|
||||||
|
LLMConnector = null,
|
||||||
|
similarityThreshold = 0.25,
|
||||||
|
topN = 4,
|
||||||
|
}) {
|
||||||
|
if (!namespace || !input || !LLMConnector)
|
||||||
|
throw new Error("Invalid request to performSimilaritySearch.");
|
||||||
|
|
||||||
|
const { client } = await this.connect();
|
||||||
|
if (!(await this.namespaceExists(client, namespace))) {
|
||||||
|
return {
|
||||||
|
contextTexts: [],
|
||||||
|
sources: [],
|
||||||
|
message:
|
||||||
|
"Invalid query - no namespace found for workspace in vector db!",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryVector = await LLMConnector.embedTextInput(input);
|
||||||
|
const { contextTexts, sourceDocuments } = await this.similarityResponse(
|
||||||
|
client,
|
||||||
|
namespace,
|
||||||
|
queryVector,
|
||||||
|
similarityThreshold,
|
||||||
|
topN
|
||||||
|
);
|
||||||
|
|
||||||
|
const sources = sourceDocuments.map((metadata, i) => {
|
||||||
|
return { ...metadata, text: contextTexts[i] };
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
contextTexts,
|
||||||
|
sources: this.curateSources(sources),
|
||||||
|
message: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
similarityResponse: async function (
|
||||||
|
client,
|
||||||
|
namespace,
|
||||||
|
queryVector,
|
||||||
|
similarityThreshold = 0.25,
|
||||||
|
topN = 4
|
||||||
|
) {
|
||||||
|
const result = {
|
||||||
|
contextTexts: [],
|
||||||
|
sourceDocuments: [],
|
||||||
|
scores: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const collection = await client.collection(namespace);
|
||||||
|
const responses = await collection
|
||||||
|
.find(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
sort: { $vector: queryVector },
|
||||||
|
limit: topN,
|
||||||
|
includeSimilarity: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
responses.forEach((response) => {
|
||||||
|
if (response.$similarity < similarityThreshold) return;
|
||||||
|
result.contextTexts.push(response.metadata.text);
|
||||||
|
result.sourceDocuments.push(response);
|
||||||
|
result.scores.push(response.$similarity);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
allNamespaces: async function (client) {
|
||||||
|
try {
|
||||||
|
let header = new Headers();
|
||||||
|
header.append("Token", client?.httpClient?.applicationToken);
|
||||||
|
header.append("Content-Type", "application/json");
|
||||||
|
|
||||||
|
let raw = JSON.stringify({
|
||||||
|
findCollections: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
let requestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
headers: header,
|
||||||
|
body: raw,
|
||||||
|
redirect: "follow",
|
||||||
|
};
|
||||||
|
|
||||||
|
const call = await fetch(client?.httpClient?.baseUrl, requestOptions);
|
||||||
|
const resp = await call?.text();
|
||||||
|
const collections = resp ? JSON.parse(resp)?.status?.collections : [];
|
||||||
|
return collections;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Astra::AllNamespace", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"namespace-stats": async function (reqBody = {}) {
|
||||||
|
const { namespace = null } = reqBody;
|
||||||
|
if (!namespace) throw new Error("namespace required");
|
||||||
|
const { client } = await this.connect();
|
||||||
|
if (!(await this.namespaceExists(client, namespace)))
|
||||||
|
throw new Error("Namespace by that name does not exist.");
|
||||||
|
const stats = await this.namespace(client, namespace);
|
||||||
|
return stats
|
||||||
|
? stats
|
||||||
|
: { message: "No stats were able to be fetched from DB for namespace" };
|
||||||
|
},
|
||||||
|
"delete-namespace": async function (reqBody = {}) {
|
||||||
|
const { namespace = null } = reqBody;
|
||||||
|
const { client } = await this.connect();
|
||||||
|
if (!(await this.namespaceExists(client, namespace)))
|
||||||
|
throw new Error("Namespace by that name does not exist.");
|
||||||
|
|
||||||
|
const details = await this.namespace(client, namespace);
|
||||||
|
await this.deleteVectorsInNamespace(client, namespace);
|
||||||
|
return {
|
||||||
|
message: `Namespace ${namespace} was deleted along with ${
|
||||||
|
details?.vectorCount || "all"
|
||||||
|
} vectors.`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
curateSources: function (sources = []) {
|
||||||
|
const documents = [];
|
||||||
|
for (const source of sources) {
|
||||||
|
if (Object.keys(source).length > 0) {
|
||||||
|
const metadata = source.hasOwnProperty("metadata")
|
||||||
|
? source.metadata
|
||||||
|
: source;
|
||||||
|
documents.push({
|
||||||
|
...metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.AstraDB = AstraDB;
|
|
@ -207,9 +207,9 @@ const LanceDb = {
|
||||||
|
|
||||||
vectors.push(vectorRecord);
|
vectors.push(vectorRecord);
|
||||||
submissions.push({
|
submissions.push({
|
||||||
|
...vectorRecord.metadata,
|
||||||
id: vectorRecord.id,
|
id: vectorRecord.id,
|
||||||
vector: vectorRecord.values,
|
vector: vectorRecord.values,
|
||||||
...vectorRecord.metadata,
|
|
||||||
});
|
});
|
||||||
documentVectors.push({ docId, vectorId: vectorRecord.id });
|
documentVectors.push({ docId, vectorId: vectorRecord.id });
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,15 @@
|
||||||
enabled "2.0.x"
|
enabled "2.0.x"
|
||||||
kuler "^2.0.0"
|
kuler "^2.0.0"
|
||||||
|
|
||||||
|
"@datastax/astra-db-ts@^0.1.3":
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@datastax/astra-db-ts/-/astra-db-ts-0.1.3.tgz#fcc25cda8d146c06278860054f09d687ff031568"
|
||||||
|
integrity sha512-7lnpym0HhUtfJVd8+vu6vYdDQpFyYof7TVLFVD2fgoIjUwj3EksFXmqDqicLAlLferZDllqSVthX9pXQ5Rdapw==
|
||||||
|
dependencies:
|
||||||
|
axios "^1.4.0"
|
||||||
|
bson "^6.2.0"
|
||||||
|
winston "^3.7.2"
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0":
|
"@eslint-community/eslint-utils@^4.2.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
|
@ -1353,6 +1362,11 @@ braces@~3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
|
bson@^6.2.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bson/-/bson-6.2.0.tgz#4b6acafc266ba18eeee111373c2699304a9ba0a3"
|
||||||
|
integrity sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==
|
||||||
|
|
||||||
btoa-lite@^1.0.0:
|
btoa-lite@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
|
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
|
||||||
|
@ -5636,7 +5650,7 @@ winston-transport@^4.5.0:
|
||||||
readable-stream "^3.6.0"
|
readable-stream "^3.6.0"
|
||||||
triple-beam "^1.3.0"
|
triple-beam "^1.3.0"
|
||||||
|
|
||||||
winston@^3.9.0:
|
winston@^3.7.2, winston@^3.9.0:
|
||||||
version "3.11.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91"
|
resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91"
|
||||||
integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==
|
integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==
|
||||||
|
|
Loading…
Reference in New Issue