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:
|
||||
- type: markdown
|
||||
attributes:
|
||||
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.
|
||||
|
||||
Want help contributing a PR? Use our repo chatbot by OnboardAI! https://learnthisrepo.com/anythingllm
|
||||
|
||||
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. Want help contributing a PR? Use our repo chatbot by OnboardAI! https://learnthisrepo.com/anythingllm"
|
||||
- type: dropdown
|
||||
id: runtime
|
||||
attributes:
|
||||
|
|
|
@ -20,6 +20,7 @@ on:
|
|||
- '.vscode/**/*'
|
||||
- '**/.env.example'
|
||||
- '.github/ISSUE_TEMPLATE/**/*'
|
||||
- 'embed/**/*' # Embed should be published to frontend (yarn build:publish) if any changes are introduced
|
||||
|
||||
jobs:
|
||||
push_multi_platform_to_registries:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"anythingllm",
|
||||
"Astra",
|
||||
"Dockerized",
|
||||
"Embeddable",
|
||||
"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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -45,10 +50,10 @@ cd server && npx prisma migrate deploy --schema=./prisma/schema.prisma
|
|||
```
|
||||
|
||||
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
|
||||
`cd collector && NODE_ENV=production index.js &`
|
||||
`cd collector && NODE_ENV=production node index.js &`
|
||||
|
||||
AnythingLLM should now be running on `http://localhost:3001`!
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ Some cool features of AnythingLLM
|
|||
**Supported Vector Databases:**
|
||||
|
||||
- [LanceDB](https://github.com/lancedb/lancedb) (default)
|
||||
- [Astra DB](https://www.datastax.com/products/datastax-astra)
|
||||
- [Pinecone](https://pinecone.io)
|
||||
- [Chroma](https://trychroma.com)
|
||||
- [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)
|
||||
# EMBEDDING_ENGINE='openai'
|
||||
# OPEN_AI_KEY=sk-xxxx
|
||||
# EMBEDDING_MODEL_PREF='text-embedding-ada-002'
|
||||
|
||||
# EMBEDDING_ENGINE='azure'
|
||||
# AZURE_OPENAI_ENDPOINT=
|
||||
|
@ -103,6 +104,11 @@ GID='1000'
|
|||
# ZILLIZ_ENDPOINT="https://sample.api.gcp-us-west1.zillizcloud.com"
|
||||
# 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
|
||||
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
|
||||
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
**/dist
|
||||
**/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>
|
||||
<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> -->
|
||||
<!--
|
||||
<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"> USE THIS SRC FOR DEVELOPMENT SO CHANGES APPEAR!
|
||||
</script>
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "anythingllm-embedded-chat",
|
||||
"private": true,
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nodemon -e js,jsx,css --watch src --exec \"yarn run dev:preview\"",
|
||||
|
@ -15,6 +15,7 @@
|
|||
"@phosphor-icons/react": "^2.0.13",
|
||||
"dompurify": "^3.0.8",
|
||||
"he": "^1.2.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"markdown-it": "^13.0.1",
|
||||
"react": "^18.2.0",
|
||||
|
@ -39,4 +40,4 @@
|
|||
"vite": "^5.0.0",
|
||||
"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();
|
||||
|
||||
useEffect(() => {
|
||||
toggleOpenChat(embedSettings.openOnLoad === "on");
|
||||
if (embedSettings.openOnLoad === "on") {
|
||||
toggleOpenChat(true);
|
||||
}
|
||||
}, [embedSettings.loaded]);
|
||||
|
||||
if (!embedSettings.loaded) return null;
|
||||
|
@ -25,9 +27,9 @@ export default function App() {
|
|||
width: isChatOpen ? 320 : "auto",
|
||||
height: isChatOpen ? "93vh" : "auto",
|
||||
}}
|
||||
className={`transition-all duration-300 ease-in-out ${
|
||||
className={`${
|
||||
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"
|
||||
}`}
|
||||
>
|
||||
|
@ -41,7 +43,7 @@ export default function App() {
|
|||
<OpenButton
|
||||
settings={embedSettings}
|
||||
isOpen={isChatOpen}
|
||||
toggleOpen={toggleOpenChat}
|
||||
toggleOpen={() => toggleOpenChat(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { memo, forwardRef } from "react";
|
|||
import { Warning } from "@phosphor-icons/react";
|
||||
// import Actions from "./Actions";
|
||||
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 createDOMPurify from "dompurify";
|
||||
|
||||
|
@ -17,11 +17,14 @@ const HistoricalMessage = forwardRef(
|
|||
error
|
||||
? "bg-red-200"
|
||||
: role === "user"
|
||||
? USER_BACKGROUND_COLOR
|
||||
: AI_BACKGROUND_COLOR
|
||||
? embedderSettings.USER_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">
|
||||
{error ? (
|
||||
<div className="p-2 rounded-lg bg-red-50 text-red-500">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { forwardRef, memo } from "react";
|
||||
import { Warning } from "@phosphor-icons/react";
|
||||
import renderMarkdown from "@/utils/chat/markdown";
|
||||
import { AI_BACKGROUND_COLOR } from "@/utils/constants";
|
||||
import { embedderSettings } from "@/main";
|
||||
|
||||
const PromptReply = forwardRef(
|
||||
({ uuid, reply, pending, error, sources = [] }, ref) => {
|
||||
|
@ -11,7 +11,7 @@ const PromptReply = forwardRef(
|
|||
return (
|
||||
<div
|
||||
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="flex gap-x-5">
|
||||
|
@ -44,9 +44,12 @@ const PromptReply = forwardRef(
|
|||
<div
|
||||
key={uuid}
|
||||
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">
|
||||
<span
|
||||
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 PromptReply from "./PromptReply";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ArrowDown } from "@phosphor-icons/react";
|
||||
import { ArrowDown, CircleNotch } from "@phosphor-icons/react";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
export default function ChatHistory({ settings = {}, history = [] }) {
|
||||
|
@ -46,10 +46,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
|||
|
||||
if (history.length === 0) {
|
||||
return (
|
||||
<div
|
||||
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="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">
|
||||
<p className="text-slate-400 text-sm font-base py-4 text-center">
|
||||
{settings?.greeting ?? "Send a chat to get started!"}
|
||||
|
@ -61,8 +58,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
|||
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
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"
|
||||
id="chat-history"
|
||||
ref={chatHistoryRef}
|
||||
>
|
||||
|
@ -98,7 +94,7 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
|||
);
|
||||
})}
|
||||
{!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="p-1 rounded-full border border-white/10 bg-white/10 hover:bg-white/20 hover:text-white">
|
||||
<ArrowDown
|
||||
|
@ -113,3 +109,15 @@ export default function ChatHistory({ settings = {}, history = [] }) {
|
|||
</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 (
|
||||
<div
|
||||
style={{ bottom: 25 }}
|
||||
className="w-full fixed md:absolute left-0 z-10 flex justify-center items-center"
|
||||
>
|
||||
<div className="w-full absolute left-0 bottom-[5px] z-10 flex justify-center items-center">
|
||||
<form
|
||||
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="flex items-center w-full">
|
||||
<textarea
|
||||
|
|
|
@ -76,7 +76,7 @@ export default function ChatContainer({
|
|||
}, [loadingResponse, chatHistory]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="h-full w-full relative">
|
||||
<ChatHistory settings={settings} history={chatHistory} />
|
||||
<PromptInput
|
||||
settings={settings}
|
||||
|
@ -86,6 +86,6 @@ export default function ChatContainer({
|
|||
inputDisabled={loadingResponse}
|
||||
buttonDisabled={loadingResponse}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import AnythingLLMLogo from "@/assets/anything-llm-dark.png";
|
||||
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";
|
||||
|
||||
export default function ChatWindowHeader({
|
||||
|
@ -46,15 +51,19 @@ export default function ChatWindowHeader({
|
|||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<OptionsMenu showing={showingOptions} resetChat={handleChatReset} />
|
||||
<OptionsMenu
|
||||
settings={settings}
|
||||
showing={showingOptions}
|
||||
resetChat={handleChatReset}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OptionsMenu({ showing, resetChat }) {
|
||||
function OptionsMenu({ settings, showing, resetChat }) {
|
||||
if (!showing) return null;
|
||||
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
|
||||
onClick={resetChat}
|
||||
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} />
|
||||
<p>Reset Chat</p>
|
||||
</button>
|
||||
<ContactSupport email={settings.supportEmail} />
|
||||
</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 useChatHistory from "@/hooks/chat/useChatHistory";
|
||||
import ChatContainer from "./ChatContainer";
|
||||
import Sponsor from "../Sponsor";
|
||||
import { ChatHistoryLoading } from "./ChatContainer/ChatHistory";
|
||||
|
||||
export default function ChatWindow({ closeChat, settings, sessionId }) {
|
||||
const { chatHistory, setChatHistory, loading } = useChatHistory(
|
||||
|
@ -9,10 +11,28 @@ export default function ChatWindow({ closeChat, settings, 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();
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col h-full">
|
||||
<ChatWindowHeader
|
||||
sessionId={sessionId}
|
||||
settings={settings}
|
||||
|
@ -25,7 +45,10 @@ export default function ChatWindow({ closeChat, settings, sessionId }) {
|
|||
settings={settings}
|
||||
knownHistory={chatHistory}
|
||||
/>
|
||||
<SessionId />
|
||||
<div className="pt-4 pb-2 h-fit gap-y-1">
|
||||
<SessionId />
|
||||
<Sponsor settings={settings} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -159,14 +159,6 @@ const customCss = `
|
|||
.bg-black-900 {
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
.bg-historical-msg-system {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.bg-historical-msg-user {
|
||||
background: #2C2F35;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Head() {
|
||||
|
|
|
@ -9,10 +9,10 @@ import {
|
|||
|
||||
const CHAT_ICONS = {
|
||||
plus: Plus,
|
||||
"chat-circle-dots": ChatCircleDots,
|
||||
headset: Headset,
|
||||
binoculars: Binoculars,
|
||||
magnifying: MagnifyingGlass,
|
||||
chatBubble: ChatCircleDots,
|
||||
support: Headset,
|
||||
search2: Binoculars,
|
||||
search: MagnifyingGlass,
|
||||
magic: MagicWand,
|
||||
};
|
||||
|
||||
|
@ -24,7 +24,7 @@ export default function OpenButton({ settings, isOpen, toggleOpen }) {
|
|||
return (
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<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";
|
||||
|
||||
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??
|
||||
return { isChatOpen: isOpen, toggleOpenChat: setOpen };
|
||||
function toggleOpenChat(newValue) {
|
||||
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",
|
||||
brandImageUrl: null, // will be forced into 100x50px container
|
||||
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
|
||||
openOnLoad: "off", // or "on"
|
||||
supportEmail: null, // string of email for contact
|
||||
};
|
||||
|
||||
export default function useGetScriptAttributes() {
|
||||
|
|
|
@ -11,6 +11,12 @@ root.render(
|
|||
</React.StrictMode>
|
||||
);
|
||||
|
||||
const scriptSettings = Object.assign(
|
||||
{},
|
||||
document?.currentScript?.dataset || {}
|
||||
);
|
||||
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 markdownIt from "markdown-it";
|
||||
import { staticHljs as hljs } from "./hljs";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
// TODO can we add back Hljs without bloating the app in a bad way?
|
||||
|
||||
const markdown = markdownIt({
|
||||
html: true,
|
||||
typographer: true,
|
||||
highlight: function (code) {
|
||||
highlight: function (code, lang) {
|
||||
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 (
|
||||
`<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">
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
// export const USER_BACKGROUND_COLOR = "bg-gray-700";
|
||||
// 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";
|
||||
export const CHAT_UI_REOPEN = "___anythingllm-chat-widget-open___";
|
||||
|
|
|
@ -1642,6 +1642,11 @@ he@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
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:
|
||||
version "2.1.0"
|
||||
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(
|
||||
() => import("@/pages/GeneralSettings/EmbedConfigs")
|
||||
);
|
||||
const EmbedChats = lazy(() => import("@/pages/Admin/Users"));
|
||||
const EmbedChats = lazy(() => import("@/pages/GeneralSettings/EmbedChats"));
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
|
|
@ -22,12 +22,27 @@ export default function OpenAiOptions({ settings }) {
|
|||
Model Preference
|
||||
</label>
|
||||
<select
|
||||
disabled={true}
|
||||
className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
name="EmbeddingModelPref"
|
||||
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}>
|
||||
text-embedding-ada-002
|
||||
</option>
|
||||
<optgroup label="Available embedding models">
|
||||
{[
|
||||
"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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -85,6 +85,7 @@ function OpenAIModelSelection({ apiKey, settings }) {
|
|||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-4",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
].map((model) => {
|
||||
|
|
|
@ -6,9 +6,14 @@ import Directory from "./Directory";
|
|||
import showToast from "../../../../utils/toast";
|
||||
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
|
||||
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({
|
||||
workspace,
|
||||
|
@ -142,10 +147,12 @@ export default function DocumentSettings({
|
|||
});
|
||||
|
||||
// Do not do cost estimation unless the embedding engine is OpenAi.
|
||||
if (
|
||||
!systemSettings?.EmbeddingEngine ||
|
||||
systemSettings.EmbeddingEngine === "openai"
|
||||
) {
|
||||
if (systemSettings?.EmbeddingEngine === "openai") {
|
||||
const COST_PER_TOKEN =
|
||||
MODEL_COSTS[
|
||||
systemSettings?.EmbeddingModelPref || "text-embedding-ada-002"
|
||||
];
|
||||
|
||||
const dollarAmount = (totalTokenCount / 1000) * COST_PER_TOKEN;
|
||||
setEmbeddingsCost(dollarAmount);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ const PROVIDER_DEFAULT_MODELS = {
|
|||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-4",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-32k",
|
||||
],
|
||||
|
|
|
@ -44,6 +44,7 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
|||
const formEl = useRef(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const defaults = recommendedSettings(settings?.LLMProvider);
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
|
@ -72,7 +73,15 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
|||
)
|
||||
)
|
||||
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
|
||||
? (window.location = paths.home())
|
||||
: window.location.reload();
|
||||
|
@ -310,7 +319,11 @@ export default function WorkspaceSettings({ active, workspace, settings }) {
|
|||
</div>
|
||||
</div>
|
||||
<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 && (
|
||||
<button
|
||||
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);
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
|
@ -337,11 +350,12 @@ function DeleteWorkspace({ workspace, onClick }) {
|
|||
if (!canDelete) return null;
|
||||
return (
|
||||
<button
|
||||
disabled={deleting}
|
||||
onClick={onClick}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
});
|
||||
},
|
||||
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;
|
||||
|
|
|
@ -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 showToast from "@/utils/toast";
|
||||
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";
|
||||
// hljs.registerLanguage('html', hljsHTML)
|
||||
|
||||
export default function CodeSnippetModal({ embed, closeModal }) {
|
||||
return (
|
||||
|
@ -51,7 +47,7 @@ function createScriptTagSnippet(embed, scriptHost, serverHost) {
|
|||
return `<!--
|
||||
Paste this script at the bottom of your HTML before the </body> tag.
|
||||
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
|
||||
data-embed-id="${embed.uuid}"
|
||||
|
|
|
@ -130,7 +130,7 @@ export const WorkspaceSelection = ({ defaultValue = null }) => {
|
|||
<div>
|
||||
<div className="flex flex-col mb-2">
|
||||
<label
|
||||
htmlFor="workspaceId"
|
||||
htmlFor="workspace_id"
|
||||
className="block text-sm font-medium text-white"
|
||||
>
|
||||
Workspace
|
||||
|
@ -141,13 +141,20 @@ export const WorkspaceSelection = ({ defaultValue = null }) => {
|
|||
</p>
|
||||
</div>
|
||||
<select
|
||||
name="workspaceId"
|
||||
name="workspace_id"
|
||||
required={true}
|
||||
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"
|
||||
>
|
||||
{workspaces.map((workspace) => {
|
||||
return <option value={workspace.id}>{workspace.name}</option>;
|
||||
return (
|
||||
<option
|
||||
selected={defaultValue === workspace.id}
|
||||
value={workspace.id}
|
||||
>
|
||||
{workspace.name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@ import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
|||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import ChangeWarningModal from "@/components/ChangeWarning";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
|
@ -23,6 +24,7 @@ import MilvusDBOptions from "@/components/VectorDBSelection/MilvusDBOptions";
|
|||
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import AstraDBOptions from "@/components/VectorDBSelection/AstraDBOptions";
|
||||
|
||||
export default function GeneralVectorDatabase() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
@ -100,6 +102,13 @@ export default function GeneralVectorDatabase() {
|
|||
options: <MilvusDBOptions settings={settings} />,
|
||||
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) => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
|||
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import ChromaLogo from "@/media/vectordbs/chroma.png";
|
||||
import PineconeLogo from "@/media/vectordbs/pinecone.png";
|
||||
import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
||||
|
@ -147,6 +148,13 @@ const VECTOR_DB_PRIVACY = {
|
|||
],
|
||||
logo: ZillizLogo,
|
||||
},
|
||||
astra: {
|
||||
name: "AstraDB",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your cloud AstraDB database.",
|
||||
],
|
||||
logo: AstraDBLogo,
|
||||
},
|
||||
lancedb: {
|
||||
name: "LanceDB",
|
||||
description: [
|
||||
|
|
|
@ -7,6 +7,7 @@ import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
|||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import System from "@/models/system";
|
||||
import paths from "@/utils/paths";
|
||||
import PineconeDBOptions from "@/components/VectorDBSelection/PineconeDBOptions";
|
||||
|
@ -16,6 +17,7 @@ import WeaviateDBOptions from "@/components/VectorDBSelection/WeaviateDBOptions"
|
|||
import LanceDBOptions from "@/components/VectorDBSelection/LanceDBOptions";
|
||||
import MilvusOptions from "@/components/VectorDBSelection/MilvusDBOptions";
|
||||
import ZillizCloudOptions from "@/components/VectorDBSelection/ZillizCloudOptions";
|
||||
import AstraDBOptions from "@/components/VectorDBSelection/AstraDBOptions";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import VectorDBItem from "@/components/VectorDBSelection/VectorDBItem";
|
||||
|
@ -100,6 +102,13 @@ export default function VectorDatabaseConnection({
|
|||
options: <MilvusOptions settings={settings} />,
|
||||
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() {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"prisma:migrate": "cd server && npx prisma migrate dev --name init",
|
||||
"prisma:seed": "cd server && npx prisma db 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:frontend": "cd frontend && yarn build",
|
||||
"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)
|
||||
# EMBEDDING_ENGINE='openai'
|
||||
# OPEN_AI_KEY=sk-xxxx
|
||||
# EMBEDDING_MODEL_PREF='text-embedding-ada-002'
|
||||
|
||||
# EMBEDDING_ENGINE='azure'
|
||||
# AZURE_OPENAI_ENDPOINT=
|
||||
|
@ -76,6 +77,11 @@ JWT_SECRET="my-random-string-for-seeding" # Please generate random string at lea
|
|||
# PINECONE_API_KEY=
|
||||
# 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.
|
||||
VECTOR_DB="lancedb"
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ storage/imports
|
|||
!storage/documents/DOCUMENTS.md
|
||||
logs/server.log
|
||||
*.db
|
||||
*.db-journal
|
||||
storage/lancedb
|
||||
public/
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ function chatEndpoints(app) {
|
|||
async (request, response) => {
|
||||
try {
|
||||
const user = await userFromSession(request, response);
|
||||
// console.log("user", user);
|
||||
const { slug } = request.params;
|
||||
const { message, mode = "query" } = reqBody(request);
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
const { EmbedChats } = require("../models/embedChats");
|
||||
const { EmbedConfig } = require("../models/embedConfig");
|
||||
const { reqBody, userFromSession } = require("../utils/http");
|
||||
const {
|
||||
validEmbedConfig,
|
||||
validEmbedConfigId,
|
||||
} = require("../utils/middleware/embedMiddleware");
|
||||
const { validEmbedConfigId } = require("../utils/middleware/embedMiddleware");
|
||||
const {
|
||||
flexUserRoleValid,
|
||||
ROLES,
|
||||
|
@ -80,14 +77,14 @@ function embedManagementEndpoints(app) {
|
|||
app.post(
|
||||
"/embed/chats",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (_, response) => {
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { offset = 0, limit = 20 } = reqBody(request);
|
||||
const embedChats = await EmbedChats.whereWithEmbed(
|
||||
const embedChats = await EmbedChats.whereWithEmbedAndWorkspace(
|
||||
{},
|
||||
limit,
|
||||
offset * limit,
|
||||
{ id: "desc" }
|
||||
{ id: "desc" },
|
||||
offset * limit
|
||||
);
|
||||
const totalChats = await EmbedChats.count();
|
||||
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 };
|
||||
|
|
|
@ -106,6 +106,9 @@ const Document = {
|
|||
await prisma.workspace_documents.delete({
|
||||
where: { id: document.id, workspaceId: workspace.id },
|
||||
});
|
||||
await prisma.document_vectors.deleteMany({
|
||||
where: { docId: document.docId },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ const EmbedChats = {
|
|||
}
|
||||
},
|
||||
|
||||
whereWithEmbed: async function (
|
||||
whereWithEmbedAndWorkspace: async function (
|
||||
clause = {},
|
||||
limit = null,
|
||||
orderBy = null,
|
||||
|
@ -124,7 +124,17 @@ const EmbedChats = {
|
|||
try {
|
||||
const chats = await prisma.embed_chats.findMany({
|
||||
where: clause,
|
||||
include: { embed_config: true },
|
||||
include: {
|
||||
embed_config: {
|
||||
select: {
|
||||
workspace: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...(limit !== null ? { take: limit } : {}),
|
||||
...(offset !== null ? { skip: offset } : {}),
|
||||
...(orderBy !== null ? { orderBy } : {}),
|
||||
|
|
|
@ -13,6 +13,7 @@ const EmbedConfig = {
|
|||
"max_chats_per_day",
|
||||
"max_chats_per_session",
|
||||
"chat_mode",
|
||||
"workspace_id",
|
||||
],
|
||||
|
||||
new: async function (data, creatorId = null) {
|
||||
|
@ -48,7 +49,7 @@ const EmbedConfig = {
|
|||
),
|
||||
createdBy: Number(creatorId) ?? null,
|
||||
workspace: {
|
||||
connect: { id: Number(data.workspaceId) },
|
||||
connect: { id: Number(data.workspace_id) },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -185,7 +186,11 @@ const BOOLEAN_KEYS = [
|
|||
"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
|
||||
function validatedCreationData(value, field) {
|
||||
|
|
|
@ -68,6 +68,12 @@ const SystemSettings = {
|
|||
ZillizApiToken: process.env.ZILLIZ_API_TOKEN,
|
||||
}
|
||||
: {}),
|
||||
...(vectorDB === "astra"
|
||||
? {
|
||||
AstraDBApplicationToken: process?.env?.ASTRA_DB_APPLICATION_TOKEN,
|
||||
AstraDBEndpoint: process?.env?.ASTRA_DB_ENDPOINT,
|
||||
}
|
||||
: {}),
|
||||
LLMProvider: llmProvider,
|
||||
...(llmProvider === "openai"
|
||||
? {
|
||||
|
|
|
@ -3,6 +3,7 @@ const slugify = require("slugify");
|
|||
const { Document } = require("./documents");
|
||||
const { WorkspaceUser } = require("./workspaceUsers");
|
||||
const { ROLES } = require("../utils/middleware/multiUserProtected");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
|
||||
const Workspace = {
|
||||
writable: [
|
||||
|
@ -22,6 +23,7 @@ const Workspace = {
|
|||
new: async function (name = null, creatorId = null) {
|
||||
if (!name) return { result: null, message: "name cannot be null" };
|
||||
var slug = slugify(name, { lower: true });
|
||||
slug = slug || uuidv4();
|
||||
|
||||
const existingBySlug = await this.get({ slug });
|
||||
if (existingBySlug !== null) {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.8.1",
|
||||
"@azure/openai": "1.0.0-beta.10",
|
||||
"@datastax/astra-db-ts": "^0.1.3",
|
||||
"@google/generative-ai": "^0.1.3",
|
||||
"@googleapis/youtube": "^9.0.0",
|
||||
"@pinecone-database/pinecone": "^2.0.1",
|
||||
|
|
|
@ -52,6 +52,8 @@ class OpenAiLLM {
|
|||
return 8192;
|
||||
case "gpt-4-1106-preview":
|
||||
return 128000;
|
||||
case "gpt-4-turbo-preview":
|
||||
return 128000;
|
||||
case "gpt-4-32k":
|
||||
return 32000;
|
||||
default:
|
||||
|
@ -65,6 +67,7 @@ class OpenAiLLM {
|
|||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-32k",
|
||||
];
|
||||
const isPreset = validModels.some((model) => modelName === model);
|
||||
|
|
|
@ -9,6 +9,7 @@ class OpenAiEmbedder {
|
|||
});
|
||||
const openai = new OpenAIApi(config);
|
||||
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
|
||||
this.maxConcurrentChunks = 500;
|
||||
|
@ -30,7 +31,7 @@ class OpenAiEmbedder {
|
|||
new Promise((resolve) => {
|
||||
this.openai
|
||||
.createEmbedding({
|
||||
model: "text-embedding-ada-002",
|
||||
model: this.model,
|
||||
input: chunk,
|
||||
})
|
||||
.then((res) => {
|
||||
|
|
|
@ -50,7 +50,7 @@ async function streamChatWithForEmbed(
|
|||
id: uuid,
|
||||
type: "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: [],
|
||||
close: true,
|
||||
error: null,
|
||||
|
|
|
@ -269,6 +269,7 @@ function handleStreamResponses(response, stream, responseProps) {
|
|||
for (const choice of event.choices) {
|
||||
const delta = choice.delta?.content;
|
||||
if (!delta) continue;
|
||||
fullText += delta;
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
|
|
|
@ -22,6 +22,9 @@ function getVectorDbClass() {
|
|||
case "zilliz":
|
||||
const { Zilliz } = require("../vectorDbProviders/zilliz");
|
||||
return Zilliz;
|
||||
case "astra":
|
||||
const { AstraDB } = require("../vectorDbProviders/astra");
|
||||
return AstraDB;
|
||||
default:
|
||||
throw new Error("ENV: No VECTOR_DB value found in environment!");
|
||||
}
|
||||
|
|
|
@ -204,6 +204,17 @@ const KEY_MAPPING = {
|
|||
checks: [isNotEmpty],
|
||||
},
|
||||
|
||||
// Astra DB Options
|
||||
|
||||
AstraDBApplicationToken: {
|
||||
envKey: "ASTRA_DB_APPLICATION_TOKEN",
|
||||
checks: [isNotEmpty],
|
||||
},
|
||||
AstraDBEndpoint: {
|
||||
envKey: "ASTRA_DB_ENDPOINT",
|
||||
checks: [isNotEmpty],
|
||||
},
|
||||
|
||||
// Together Ai Options
|
||||
TogetherAiApiKey: {
|
||||
envKey: "TOGETHER_AI_API_KEY",
|
||||
|
@ -322,6 +333,7 @@ function supportedVectorDB(input = "") {
|
|||
"qdrant",
|
||||
"milvus",
|
||||
"zilliz",
|
||||
"astra",
|
||||
];
|
||||
return supported.includes(input)
|
||||
? null
|
||||
|
|
|
@ -21,9 +21,8 @@ async function validEmbedConfig(request, response, next) {
|
|||
|
||||
function setConnectionMeta(request, response, next) {
|
||||
response.locals.connection = {
|
||||
host: request.hostname,
|
||||
path: request.path,
|
||||
ip: request.ip,
|
||||
host: request.headers?.origin,
|
||||
ip: request?.ip,
|
||||
};
|
||||
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);
|
||||
submissions.push({
|
||||
...vectorRecord.metadata,
|
||||
id: vectorRecord.id,
|
||||
vector: vectorRecord.values,
|
||||
...vectorRecord.metadata,
|
||||
});
|
||||
documentVectors.push({ docId, vectorId: vectorRecord.id });
|
||||
}
|
||||
|
|
|
@ -174,6 +174,15 @@
|
|||
enabled "2.0.x"
|
||||
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":
|
||||
version "4.4.0"
|
||||
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:
|
||||
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:
|
||||
version "1.0.0"
|
||||
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"
|
||||
triple-beam "^1.3.0"
|
||||
|
||||
winston@^3.9.0:
|
||||
winston@^3.7.2, winston@^3.9.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91"
|
||||
integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==
|
||||
|
|
Loading…
Reference in New Issue