mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-19 12:40:09 +01:00
Move embed
to submodule (#2163)
* Move `embed` to submodule * update README
This commit is contained in:
parent
2de9e492ec
commit
3a3399af94
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[submodule "embed"]
|
||||||
|
branch = main
|
||||||
|
path = embed
|
||||||
|
url = git@github.com:Mintplex-Labs/anythingllm-embed.git
|
@ -137,7 +137,7 @@ This monorepo consists of three main sections:
|
|||||||
- `server`: A NodeJS express server to handle all the interactions and do all the vectorDB management and LLM interactions.
|
- `server`: A NodeJS express server to handle all the interactions and do all the vectorDB management and LLM interactions.
|
||||||
- `collector`: NodeJS express server that process and parses documents from the UI.
|
- `collector`: NodeJS express server that process and parses documents from the UI.
|
||||||
- `docker`: Docker instructions and build process + information for building from source.
|
- `docker`: Docker instructions and build process + information for building from source.
|
||||||
- `embed`: Code specifically for generation of the [embed widget](./embed/README.md).
|
- `embed`: Submodule specifically for generation & creation of the [web embed widget](https://github.com/Mintplex-Labs/anythingllm-embed).
|
||||||
|
|
||||||
## 🛳 Self Hosting
|
## 🛳 Self Hosting
|
||||||
|
|
||||||
|
1
embed
Submodule
1
embed
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 22a0848d58e3a758d85d93d9204a72a65854ea94
|
25
embed/.gitignore
vendored
25
embed/.gitignore
vendored
@ -1,25 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
!yarn.lock
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
112
embed/README.md
112
embed/README.md
@ -1,112 +0,0 @@
|
|||||||
# AnythingLLM Embedded Chat Widget
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> The use of the AnythingLLM embed is currently in beta. Please request a feature or
|
|
||||||
> report a bug via a Github Issue if you have any issues.
|
|
||||||
|
|
||||||
> [!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`, `chatBubble`, `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.
|
|
||||||
|
|
||||||
- `data-position` - Adjust the positioning of the embed chat widget and open chat button. Default `bottom-right`. Options are `bottom-right`, `bottom-left`, `top-right`, `top-left`.
|
|
||||||
|
|
||||||
- `data-assistant-name` - Set the chat assistant name that appears above each chat message. Default `AnythingLLM Chat Assistant`
|
|
||||||
|
|
||||||
- `data-assistant-icon` - Set the icon of the chat assistant.
|
|
||||||
|
|
||||||
- `data-window-height` - Set the chat window height. **must include CSS suffix:** `px`,`%`,`rem`
|
|
||||||
|
|
||||||
- `data-window-width` - Set the chat window width. **must include CSS suffix:** `px`,`%`,`rem`
|
|
||||||
|
|
||||||
- `data-text-size` - Set the text size of the chats in pixels.
|
|
||||||
|
|
||||||
- `data-username` - A specific readable name or identifier for the client for your reference. Will be shown in AnythingLLM chat logs. If empty it will not be reported.
|
|
||||||
|
|
||||||
- `data-default-messages` - A string of comma-separated messages you want to display to the user when the chat widget has no history. Example: `"How are you?, What is so interesting about this project?, Tell me a joke."`
|
|
||||||
|
|
||||||
**Behavior Overrides**
|
|
||||||
|
|
||||||
- `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user. To enable set this attribute to `on`. All other values will be ignored.
|
|
||||||
|
|
||||||
- `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_
|
|
@ -1,17 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
</head>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<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"> USE THIS SRC FOR DEVELOPMENT SO CHANGES APPEAR!
|
|
||||||
</script>
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "esnext",
|
|
||||||
"jsx": "react",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "anythingllm-embedded-chat",
|
|
||||||
"private": false,
|
|
||||||
"license": "MIT",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "nodemon -e js,jsx,css --watch src --exec \"yarn run dev:preview\"",
|
|
||||||
"dev:preview": "yarn run dev:build && yarn serve . -p 3080 --no-clipboard",
|
|
||||||
"dev:build": "vite build && yarn styles",
|
|
||||||
"styles": "npx cleancss -o dist/anythingllm-chat-widget.min.css dist/style.css",
|
|
||||||
"build": "vite build && yarn styles && npx terser --compress -o dist/anythingllm-chat-widget.min.js -- dist/anythingllm-chat-widget.js",
|
|
||||||
"build:publish": "yarn build:publish:js && yarn build:publish:css",
|
|
||||||
"build:publish:js": "yarn build && mkdir -p ../frontend/public/embed && cp -r dist/anythingllm-chat-widget.min.js ../frontend/public/embed/anythingllm-chat-widget.min.js",
|
|
||||||
"build:publish:css": "cp -r dist/anythingllm-chat-widget.min.css ../frontend/public/embed/anythingllm-chat-widget.min.css",
|
|
||||||
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./src"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@microsoft/fetch-event-source": "^2.0.1",
|
|
||||||
"@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",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-image": "^3.0.3",
|
|
||||||
"@types/react": "^18.2.37",
|
|
||||||
"@types/react-dom": "^18.2.15",
|
|
||||||
"@vitejs/plugin-react": "^4.2.0",
|
|
||||||
"autoprefixer": "^10.4.14",
|
|
||||||
"clean-css": "^5.3.3",
|
|
||||||
"clean-css-cli": "^5.6.3",
|
|
||||||
"eslint": "^8.53.0",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
|
||||||
"globals": "^13.21.0",
|
|
||||||
"nodemon": "^2.0.22",
|
|
||||||
"postcss": "^8.4.23",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"serve": "^14.2.1",
|
|
||||||
"tailwindcss": "3.4.1",
|
|
||||||
"terser": "^5.27.0",
|
|
||||||
"vite": "^5.0.0",
|
|
||||||
"vite-plugin-singlefile": "^0.13.5"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
import tailwind from 'tailwindcss'
|
|
||||||
import autoprefixer from 'autoprefixer'
|
|
||||||
import tailwindConfig from './tailwind.config.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
plugins: [
|
|
||||||
tailwind(tailwindConfig),
|
|
||||||
autoprefixer,
|
|
||||||
],
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
// 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`)
|
|
@ -1,71 +0,0 @@
|
|||||||
import useGetScriptAttributes from "@/hooks/useScriptAttributes";
|
|
||||||
import useSessionId from "@/hooks/useSessionId";
|
|
||||||
import useOpenChat from "@/hooks/useOpen";
|
|
||||||
import Head from "@/components/Head";
|
|
||||||
import OpenButton from "@/components/OpenButton";
|
|
||||||
import ChatWindow from "./components/ChatWindow";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
const { isChatOpen, toggleOpenChat } = useOpenChat();
|
|
||||||
const embedSettings = useGetScriptAttributes();
|
|
||||||
const sessionId = useSessionId();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (embedSettings.openOnLoad === "on") {
|
|
||||||
toggleOpenChat(true);
|
|
||||||
}
|
|
||||||
}, [embedSettings.loaded]);
|
|
||||||
|
|
||||||
if (!embedSettings.loaded) return null;
|
|
||||||
|
|
||||||
const positionClasses = {
|
|
||||||
"bottom-left": "allm-bottom-0 allm-left-0 allm-ml-4",
|
|
||||||
"bottom-right": "allm-bottom-0 allm-right-0 allm-mr-4",
|
|
||||||
"top-left": "allm-top-0 allm-left-0 allm-ml-4 allm-mt-4",
|
|
||||||
"top-right": "allm-top-0 allm-right-0 allm-mr-4 allm-mt-4",
|
|
||||||
};
|
|
||||||
|
|
||||||
const position = embedSettings.position || "bottom-right";
|
|
||||||
const windowWidth = embedSettings.windowWidth ?? "400px";
|
|
||||||
const windowHeight = embedSettings.windowHeight ?? "700px";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head />
|
|
||||||
<div
|
|
||||||
id="anything-llm-embed-chat-container"
|
|
||||||
className={`allm-fixed allm-inset-0 allm-z-50 ${isChatOpen ? "allm-block" : "allm-hidden"}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
maxWidth: windowWidth,
|
|
||||||
maxHeight: windowHeight,
|
|
||||||
}}
|
|
||||||
className={`allm-h-full allm-w-full allm-bg-white allm-fixed allm-bottom-0 allm-right-0 allm-mb-4 allm-md:mr-4 allm-rounded-2xl allm-border allm-border-gray-300 allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)] ${positionClasses[position]}`}
|
|
||||||
id="anything-llm-chat"
|
|
||||||
>
|
|
||||||
{isChatOpen && (
|
|
||||||
<ChatWindow
|
|
||||||
closeChat={() => toggleOpenChat(false)}
|
|
||||||
settings={embedSettings}
|
|
||||||
sessionId={sessionId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!isChatOpen && (
|
|
||||||
<div
|
|
||||||
id="anything-llm-embed-chat-button-container"
|
|
||||||
className={`allm-fixed allm-bottom-0 ${positionClasses[position]} allm-mb-4 allm-z-50`}
|
|
||||||
>
|
|
||||||
<OpenButton
|
|
||||||
settings={embedSettings}
|
|
||||||
isOpen={isChatOpen}
|
|
||||||
toggleOpen={() => toggleOpenChat(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.2 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.3 KiB |
@ -1,43 +0,0 @@
|
|||||||
import useCopyText from "@/hooks/useCopyText";
|
|
||||||
import { Check, ClipboardText } from "@phosphor-icons/react";
|
|
||||||
import { memo } from "react";
|
|
||||||
import { Tooltip } from "react-tooltip";
|
|
||||||
|
|
||||||
const Actions = ({ message }) => {
|
|
||||||
return (
|
|
||||||
<div className="allm-flex allm-justify-start allm-items-center allm-gap-x-4">
|
|
||||||
<CopyMessage message={message} />
|
|
||||||
{/* Other actions to go here later. */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function CopyMessage({ message }) {
|
|
||||||
const { copied, copyText } = useCopyText();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="allm-mt-3 allm-relative">
|
|
||||||
<button
|
|
||||||
data-tooltip-id="copy-assistant-text"
|
|
||||||
data-tooltip-content="Copy"
|
|
||||||
className="allm-border-none allm-text-zinc-300"
|
|
||||||
onClick={() => copyText(message)}
|
|
||||||
>
|
|
||||||
{copied ? (
|
|
||||||
<Check size={18} className="allm-mb-1" />
|
|
||||||
) : (
|
|
||||||
<ClipboardText size={18} className="allm-mb-1" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
id="copy-assistant-text"
|
|
||||||
place="bottom"
|
|
||||||
delayShow={300}
|
|
||||||
className="allm-tooltip !allm-text-xs"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(Actions);
|
|
@ -1,97 +0,0 @@
|
|||||||
import React, { memo, forwardRef } from "react";
|
|
||||||
import { Warning } from "@phosphor-icons/react";
|
|
||||||
import renderMarkdown from "@/utils/chat/markdown";
|
|
||||||
import { embedderSettings } from "@/main";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import createDOMPurify from "dompurify";
|
|
||||||
import AnythingLLMIcon from "@/assets/anything-llm-icon.svg";
|
|
||||||
import { formatDate } from "@/utils/date";
|
|
||||||
|
|
||||||
const DOMPurify = createDOMPurify(window);
|
|
||||||
const HistoricalMessage = forwardRef(
|
|
||||||
(
|
|
||||||
{ uuid = v4(), message, role, sources = [], error = false, sentAt },
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const textSize = !!embedderSettings.settings.textSize
|
|
||||||
? `allm-text-[${embedderSettings.settings.textSize}px]`
|
|
||||||
: "allm-text-sm";
|
|
||||||
if (error) console.error(`ANYTHING_LLM_CHAT_WIDGET_ERROR: ${error}`);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="py-[5px]">
|
|
||||||
{role === "assistant" && (
|
|
||||||
<div
|
|
||||||
className={`allm-text-[10px] allm-text-gray-400 allm-ml-[54px] allm-mr-6 allm-mb-2 allm-text-left allm-font-sans`}
|
|
||||||
>
|
|
||||||
{embedderSettings.settings.assistantName ||
|
|
||||||
"Anything LLM Chat Assistant"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
key={uuid}
|
|
||||||
ref={ref}
|
|
||||||
className={`allm-flex allm-items-start allm-w-full allm-h-fit ${
|
|
||||||
role === "user" ? "allm-justify-end" : "allm-justify-start"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{role === "assistant" && (
|
|
||||||
<img
|
|
||||||
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
|
|
||||||
alt="Anything LLM Icon"
|
|
||||||
className="allm-w-9 allm-h-9 allm-flex-shrink-0 allm-ml-2 allm-mt-2"
|
|
||||||
id="anything-llm-icon"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
wordBreak: "break-word",
|
|
||||||
backgroundColor:
|
|
||||||
role === "user"
|
|
||||||
? embedderSettings.USER_STYLES.msgBg
|
|
||||||
: embedderSettings.ASSISTANT_STYLES.msgBg,
|
|
||||||
}}
|
|
||||||
className={`allm-py-[11px] allm-px-4 allm-flex allm-flex-col allm-font-sans ${
|
|
||||||
error
|
|
||||||
? "allm-bg-red-200 allm-rounded-lg allm-mr-[37px] allm-ml-[9px]"
|
|
||||||
: role === "user"
|
|
||||||
? `${embedderSettings.USER_STYLES.base} allm-anything-llm-user-message`
|
|
||||||
: `${embedderSettings.ASSISTANT_STYLES.base} allm-anything-llm-assistant-message`
|
|
||||||
} allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
|
|
||||||
>
|
|
||||||
<div className="allm-flex">
|
|
||||||
{error ? (
|
|
||||||
<div className="allm-p-2 allm-rounded-lg allm-bg-red-50 allm-text-red-500">
|
|
||||||
<span className={`allm-inline-block `}>
|
|
||||||
<Warning className="allm-h-4 allm-w-4 allm-mb-1 allm-inline-block" />{" "}
|
|
||||||
Could not respond to message.
|
|
||||||
</span>
|
|
||||||
<p className="allm-text-xs allm-font-mono allm-mt-2 allm-border-l-2 allm-border-red-500 allm-pl-2 allm-bg-red-300 allm-p-2 allm-rounded-sm">
|
|
||||||
Server error
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
className={`allm-whitespace-pre-line allm-flex allm-flex-col allm-gap-y-1 ${textSize} allm-leading-[20px]`}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(renderMarkdown(message)),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{sentAt && (
|
|
||||||
<div
|
|
||||||
className={`allm-font-sans allm-text-[10px] allm-text-gray-400 allm-ml-[54px] allm-mr-6 allm-mt-2 ${role === "user" ? "allm-text-right" : "allm-text-left"}`}
|
|
||||||
>
|
|
||||||
{formatDate(sentAt)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default memo(HistoricalMessage);
|
|
@ -1,111 +0,0 @@
|
|||||||
import { forwardRef, memo } from "react";
|
|
||||||
import { Warning } from "@phosphor-icons/react";
|
|
||||||
import renderMarkdown from "@/utils/chat/markdown";
|
|
||||||
import { embedderSettings } from "@/main";
|
|
||||||
import AnythingLLMIcon from "@/assets/anything-llm-icon.svg";
|
|
||||||
import { formatDate } from "@/utils/date";
|
|
||||||
|
|
||||||
const PromptReply = forwardRef(
|
|
||||||
({ uuid, reply, pending, error, sources = [] }, ref) => {
|
|
||||||
if (!reply && sources.length === 0 && !pending && !error) return null;
|
|
||||||
if (error) console.error(`ANYTHING_LLM_CHAT_WIDGET_ERROR: ${error}`);
|
|
||||||
|
|
||||||
if (pending) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`allm-flex allm-items-start allm-w-full allm-h-fit allm-justify-start`}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
|
|
||||||
alt="Anything LLM Icon"
|
|
||||||
className="allm-w-9 allm-h-9 allm-flex-shrink-0 allm-ml-2"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
wordBreak: "break-word",
|
|
||||||
backgroundColor: embedderSettings.ASSISTANT_STYLES.msgBg,
|
|
||||||
}}
|
|
||||||
className={`allm-py-[11px] allm-px-4 allm-flex allm-flex-col ${embedderSettings.ASSISTANT_STYLES.base} allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
|
|
||||||
>
|
|
||||||
<div className="allm-flex allm-gap-x-5">
|
|
||||||
<div className="allm-mx-4 allm-my-1 allm-dot-falling"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`allm-flex allm-items-end allm-w-full allm-h-fit allm-justify-start`}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
|
|
||||||
alt="Anything LLM Icon"
|
|
||||||
className="allm-w-9 allm-h-9 allm-flex-shrink-0 allm-ml-2"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{ wordBreak: "break-word" }}
|
|
||||||
className={`allm-py-[11px] allm-px-4 allm-rounded-lg allm-flex allm-flex-col allm-bg-red-200 allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)] allm-mr-[37px] allm-ml-[9px]`}
|
|
||||||
>
|
|
||||||
<div className="allm-flex allm-gap-x-5">
|
|
||||||
<span
|
|
||||||
className={`allm-inline-block allm-p-2 allm-rounded-lg allm-bg-red-50 allm-text-red-500`}
|
|
||||||
>
|
|
||||||
<Warning className="allm-h-4 allm-w-4 allm-mb-1 allm-inline-block" />{" "}
|
|
||||||
Could not respond to message.
|
|
||||||
<span className="allm-text-xs">Server error</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-py-[5px]">
|
|
||||||
<div
|
|
||||||
className={`allm-text-[10px] allm-text-gray-400 allm-ml-[54px] allm-mr-6 allm-mb-2 allm-text-left allm-font-sans`}
|
|
||||||
>
|
|
||||||
{embedderSettings.settings.assistantName ||
|
|
||||||
"Anything LLM Chat Assistant"}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key={uuid}
|
|
||||||
ref={ref}
|
|
||||||
className={`allm-flex allm-items-start allm-w-full allm-h-fit allm-justify-start`}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
|
|
||||||
alt="Anything LLM Icon"
|
|
||||||
className="allm-w-9 allm-h-9 allm-flex-shrink-0 allm-ml-2"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
wordBreak: "break-word",
|
|
||||||
backgroundColor: embedderSettings.ASSISTANT_STYLES.msgBg,
|
|
||||||
}}
|
|
||||||
className={`allm-py-[11px] allm-px-4 allm-flex allm-flex-col ${
|
|
||||||
error ? "allm-bg-red-200" : embedderSettings.ASSISTANT_STYLES.base
|
|
||||||
} allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
|
|
||||||
>
|
|
||||||
<div className="allm-flex allm-gap-x-5">
|
|
||||||
<span
|
|
||||||
className={`allm-font-sans allm-reply allm-whitespace-pre-line allm-font-normal allm-text-sm allm-md:text-sm allm-flex allm-flex-col allm-gap-y-1`}
|
|
||||||
dangerouslySetInnerHTML={{ __html: renderMarkdown(reply) }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`allm-text-[10px] allm-text-gray-400 allm-ml-[54px] allm-mr-6 allm-mt-2 allm-text-left allm-font-sans`}
|
|
||||||
>
|
|
||||||
{formatDate(Date.now() / 1000)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default memo(PromptReply);
|
|
@ -1,163 +0,0 @@
|
|||||||
import HistoricalMessage from "./HistoricalMessage";
|
|
||||||
import PromptReply from "./PromptReply";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { ArrowDown, CircleNotch } from "@phosphor-icons/react";
|
|
||||||
import { embedderSettings } from "@/main";
|
|
||||||
import debounce from "lodash.debounce";
|
|
||||||
import { SEND_TEXT_EVENT } from "..";
|
|
||||||
|
|
||||||
export default function ChatHistory({ settings = {}, history = [] }) {
|
|
||||||
const replyRef = useRef(null);
|
|
||||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
||||||
const chatHistoryRef = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (!chatHistoryRef.current) return;
|
|
||||||
const diff =
|
|
||||||
chatHistoryRef.current.scrollHeight -
|
|
||||||
chatHistoryRef.current.scrollTop -
|
|
||||||
chatHistoryRef.current.clientHeight;
|
|
||||||
// Fuzzy margin for what qualifies as "bottom". Stronger than straight comparison since that may change over time.
|
|
||||||
const isBottom = diff <= 40;
|
|
||||||
setIsAtBottom(isBottom);
|
|
||||||
};
|
|
||||||
|
|
||||||
const debouncedScroll = debounce(handleScroll, 100);
|
|
||||||
useEffect(() => {
|
|
||||||
function watchScrollEvent() {
|
|
||||||
if (!chatHistoryRef.current) return null;
|
|
||||||
const chatHistoryElement = chatHistoryRef.current;
|
|
||||||
if (!chatHistoryElement) return null;
|
|
||||||
chatHistoryElement.addEventListener("scroll", debouncedScroll);
|
|
||||||
}
|
|
||||||
watchScrollEvent();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
if (chatHistoryRef.current) {
|
|
||||||
chatHistoryRef.current.scrollTo({
|
|
||||||
top: chatHistoryRef.current.scrollHeight,
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (history.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="allm-pb-[100px] allm-pt-[5px] allm-rounded-lg allm-px-2 allm-h-full allm-mt-2 allm-gap-y-2 allm-overflow-y-scroll allm-flex allm-flex-col allm-justify-start allm-no-scroll">
|
|
||||||
<div className="allm-flex allm-h-full allm-flex-col allm-items-center allm-justify-center">
|
|
||||||
<p className="allm-text-slate-400 allm-text-sm allm-font-sans allm-py-4 allm-text-center">
|
|
||||||
{settings?.greeting ?? "Send a chat to get started."}
|
|
||||||
</p>
|
|
||||||
<SuggestedMessages settings={settings} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="allm-pb-[30px] allm-pt-[5px] allm-rounded-lg allm-px-2 allm-h-full allm-gap-y-2 allm-overflow-y-scroll allm-flex allm-flex-col allm-justify-start allm-no-scroll allm-md:max-h-[500px]"
|
|
||||||
id="chat-history"
|
|
||||||
ref={chatHistoryRef}
|
|
||||||
>
|
|
||||||
{history.map((props, index) => {
|
|
||||||
const isLastMessage = index === history.length - 1;
|
|
||||||
const isLastBotReply =
|
|
||||||
index === history.length - 1 && props.role === "assistant";
|
|
||||||
|
|
||||||
if (isLastBotReply && props.animate) {
|
|
||||||
return (
|
|
||||||
<PromptReply
|
|
||||||
key={props.uuid}
|
|
||||||
ref={isLastMessage ? replyRef : null}
|
|
||||||
uuid={props.uuid}
|
|
||||||
reply={props.content}
|
|
||||||
pending={props.pending}
|
|
||||||
sources={props.sources}
|
|
||||||
error={props.error}
|
|
||||||
closed={props.closed}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HistoricalMessage
|
|
||||||
key={index}
|
|
||||||
ref={isLastMessage ? replyRef : null}
|
|
||||||
message={props.content}
|
|
||||||
sentAt={props.sentAt || Date.now() / 1000}
|
|
||||||
role={props.role}
|
|
||||||
sources={props.sources}
|
|
||||||
chatId={props.chatId}
|
|
||||||
feedbackScore={props.feedbackScore}
|
|
||||||
error={props.error}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{!isAtBottom && (
|
|
||||||
<div className="allm-fixed allm-bottom-[10rem] allm-right-[50px] allm-z-50 allm-cursor-pointer allm-animate-pulse">
|
|
||||||
<div className="allm-flex allm-flex-col allm-items-center">
|
|
||||||
<div className="allm-p-1 allm-rounded-full allm-border allm-border-white/10 allm-bg-black/20 hover:allm-bg-black/50">
|
|
||||||
<ArrowDown
|
|
||||||
weight="bold"
|
|
||||||
className="allm-text-white/50 allm-w-5 allm-h-5"
|
|
||||||
onClick={scrollToBottom}
|
|
||||||
id="scroll-to-bottom-button"
|
|
||||||
aria-label="Scroll to bottom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ChatHistoryLoading() {
|
|
||||||
return (
|
|
||||||
<div className="allm-h-full allm-w-full allm-relative">
|
|
||||||
<div className="allm-h-full allm-max-h-[82vh] allm-pb-[100px] allm-pt-[5px] allm-bg-gray-100 allm-rounded-lg allm-px-2 allm-h-full allm-mt-2 allm-gap-y-2 allm-overflow-y-scroll allm-flex allm-flex-col allm-justify-start allm-no-scroll">
|
|
||||||
<div className="allm-flex allm-h-full allm-flex-col allm-items-center allm-justify-center">
|
|
||||||
<CircleNotch
|
|
||||||
size={14}
|
|
||||||
className="allm-text-slate-400 allm-animate-spin"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SuggestedMessages({ settings }) {
|
|
||||||
if (!settings?.defaultMessages?.length) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-flex allm-flex-col allm-gap-y-2 allm-w-[75%]">
|
|
||||||
{settings.defaultMessages.map((content, i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
style={{
|
|
||||||
opacity: 0,
|
|
||||||
wordBreak: "break-word",
|
|
||||||
backgroundColor: embedderSettings.USER_STYLES.msgBg,
|
|
||||||
fontSize: settings.textSize,
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent(SEND_TEXT_EVENT, { detail: { command: content } })
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={`msg-suggestion allm-border-none hover:allm-shadow-[0_4px_14px_rgba(0,0,0,0.5)] allm-cursor-pointer allm-px-2 allm-py-2 allm-rounded-lg allm-text-white allm-w-full allm-shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
import { CircleNotch, PaperPlaneRight } from "@phosphor-icons/react";
|
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
|
||||||
|
|
||||||
export default function PromptInput({
|
|
||||||
message,
|
|
||||||
submit,
|
|
||||||
onChange,
|
|
||||||
inputDisabled,
|
|
||||||
buttonDisabled,
|
|
||||||
}) {
|
|
||||||
const formRef = useRef(null);
|
|
||||||
const textareaRef = useRef(null);
|
|
||||||
const [_, setFocused] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!inputDisabled && textareaRef.current) {
|
|
||||||
textareaRef.current.focus();
|
|
||||||
}
|
|
||||||
resetTextAreaHeight();
|
|
||||||
}, [inputDisabled]);
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
setFocused(false);
|
|
||||||
submit(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetTextAreaHeight = () => {
|
|
||||||
if (textareaRef.current) {
|
|
||||||
textareaRef.current.style.height = "auto";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const captureEnter = (event) => {
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
if (!event.shiftKey) {
|
|
||||||
submit(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const adjustTextArea = (event) => {
|
|
||||||
const element = event.target;
|
|
||||||
element.style.height = "auto";
|
|
||||||
element.style.height =
|
|
||||||
event.target.value.length !== 0 ? element.scrollHeight + "px" : "auto";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-w-full allm-sticky allm-bottom-0 allm-z-10 allm-flex allm-justify-center allm-items-center allm-bg-white">
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
className="allm-flex allm-flex-col allm-gap-y-1 allm-rounded-t-lg allm-w-full allm-items-center allm-justify-center"
|
|
||||||
>
|
|
||||||
<div className="allm-flex allm-items-center allm-w-full">
|
|
||||||
<div className="allm-bg-white allm-flex allm-flex-col allm-px-4 allm-overflow-hidden allm-w-full">
|
|
||||||
<div
|
|
||||||
style={{ border: "1.5px solid #22262833" }}
|
|
||||||
className="allm-flex allm-items-center allm-w-full allm-rounded-2xl"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
ref={textareaRef}
|
|
||||||
onKeyUp={adjustTextArea}
|
|
||||||
onKeyDown={captureEnter}
|
|
||||||
onChange={onChange}
|
|
||||||
required={true}
|
|
||||||
disabled={inputDisabled}
|
|
||||||
onFocus={() => setFocused(true)}
|
|
||||||
onBlur={(e) => {
|
|
||||||
setFocused(false);
|
|
||||||
adjustTextArea(e);
|
|
||||||
}}
|
|
||||||
value={message}
|
|
||||||
className="allm-font-sans allm-border-none allm-cursor-text allm-max-h-[100px] allm-text-[14px] allm-mx-2 allm-py-2 allm-w-full allm-text-black allm-bg-transparent placeholder:allm-text-slate-800/60 allm-resize-none active:allm-outline-none focus:allm-outline-none allm-flex-grow"
|
|
||||||
placeholder={"Send a message"}
|
|
||||||
id="message-input"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
ref={formRef}
|
|
||||||
type="submit"
|
|
||||||
disabled={buttonDisabled}
|
|
||||||
className="allm-bg-transparent allm-border-none allm-inline-flex allm-justify-center allm-rounded-2xl allm-cursor-pointer allm-text-black group"
|
|
||||||
id="send-message-button"
|
|
||||||
aria-label="Send message"
|
|
||||||
>
|
|
||||||
{buttonDisabled ? (
|
|
||||||
<CircleNotch className="allm-w-4 allm-h-4 allm-animate-spin" />
|
|
||||||
) : (
|
|
||||||
<PaperPlaneRight
|
|
||||||
size={24}
|
|
||||||
className="allm-my-3 allm-text-[#22262899]/60 group-hover:allm-text-[#22262899]/90"
|
|
||||||
weight="fill"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="allm-sr-only">Send message</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import ChatHistory from "./ChatHistory";
|
|
||||||
import PromptInput from "./PromptInput";
|
|
||||||
import handleChat from "@/utils/chat";
|
|
||||||
import ChatService from "@/models/chatService";
|
|
||||||
export const SEND_TEXT_EVENT = "anythingllm-embed-send-prompt";
|
|
||||||
|
|
||||||
export default function ChatContainer({
|
|
||||||
sessionId,
|
|
||||||
settings,
|
|
||||||
knownHistory = [],
|
|
||||||
}) {
|
|
||||||
const [message, setMessage] = useState("");
|
|
||||||
const [loadingResponse, setLoadingResponse] = useState(false);
|
|
||||||
const [chatHistory, setChatHistory] = useState(knownHistory);
|
|
||||||
|
|
||||||
// Resync history if the ref to known history changes
|
|
||||||
// eg: cleared.
|
|
||||||
useEffect(() => {
|
|
||||||
if (knownHistory.length !== chatHistory.length)
|
|
||||||
setChatHistory([...knownHistory]);
|
|
||||||
}, [knownHistory]);
|
|
||||||
|
|
||||||
const handleMessageChange = (event) => {
|
|
||||||
setMessage(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!message || message === "") return false;
|
|
||||||
|
|
||||||
const prevChatHistory = [
|
|
||||||
...chatHistory,
|
|
||||||
{ content: message, role: "user" },
|
|
||||||
{
|
|
||||||
content: "",
|
|
||||||
role: "assistant",
|
|
||||||
pending: true,
|
|
||||||
userMessage: message,
|
|
||||||
animate: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
setChatHistory(prevChatHistory);
|
|
||||||
setMessage("");
|
|
||||||
setLoadingResponse(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendCommand = (command, history = [], attachments = []) => {
|
|
||||||
if (!command || command === "") return false;
|
|
||||||
|
|
||||||
let prevChatHistory;
|
|
||||||
if (history.length > 0) {
|
|
||||||
// use pre-determined history chain.
|
|
||||||
prevChatHistory = [
|
|
||||||
...history,
|
|
||||||
{
|
|
||||||
content: "",
|
|
||||||
role: "assistant",
|
|
||||||
pending: true,
|
|
||||||
userMessage: command,
|
|
||||||
attachments,
|
|
||||||
animate: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
prevChatHistory = [
|
|
||||||
...chatHistory,
|
|
||||||
{
|
|
||||||
content: command,
|
|
||||||
role: "user",
|
|
||||||
attachments,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: "",
|
|
||||||
role: "assistant",
|
|
||||||
pending: true,
|
|
||||||
userMessage: command,
|
|
||||||
animate: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
setChatHistory(prevChatHistory);
|
|
||||||
setLoadingResponse(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchReply() {
|
|
||||||
const promptMessage =
|
|
||||||
chatHistory.length > 0 ? chatHistory[chatHistory.length - 1] : null;
|
|
||||||
const remHistory = chatHistory.length > 0 ? chatHistory.slice(0, -1) : [];
|
|
||||||
var _chatHistory = [...remHistory];
|
|
||||||
|
|
||||||
if (!promptMessage || !promptMessage?.userMessage) {
|
|
||||||
setLoadingResponse(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ChatService.streamChat(
|
|
||||||
sessionId,
|
|
||||||
settings,
|
|
||||||
promptMessage.userMessage,
|
|
||||||
(chatResult) =>
|
|
||||||
handleChat(
|
|
||||||
chatResult,
|
|
||||||
setLoadingResponse,
|
|
||||||
setChatHistory,
|
|
||||||
remHistory,
|
|
||||||
_chatHistory
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingResponse === true && fetchReply();
|
|
||||||
}, [loadingResponse, chatHistory]);
|
|
||||||
|
|
||||||
const handleAutofillEvent = (event) => {
|
|
||||||
if (!event.detail.command) return;
|
|
||||||
sendCommand(event.detail.command, [], []);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener(SEND_TEXT_EVENT, handleAutofillEvent);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener(SEND_TEXT_EVENT, handleAutofillEvent);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-h-full allm-w-full allm-flex allm-flex-col">
|
|
||||||
<div className="allm-flex-grow allm-overflow-y-auto">
|
|
||||||
<ChatHistory settings={settings} history={chatHistory} />
|
|
||||||
</div>
|
|
||||||
<PromptInput
|
|
||||||
message={message}
|
|
||||||
submit={handleSubmit}
|
|
||||||
onChange={handleMessageChange}
|
|
||||||
inputDisabled={loadingResponse}
|
|
||||||
buttonDisabled={loadingResponse}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
import AnythingLLMIcon from "@/assets/anything-llm-icon.svg";
|
|
||||||
import ChatService from "@/models/chatService";
|
|
||||||
import {
|
|
||||||
ArrowCounterClockwise,
|
|
||||||
Check,
|
|
||||||
Copy,
|
|
||||||
DotsThreeOutlineVertical,
|
|
||||||
Envelope,
|
|
||||||
X,
|
|
||||||
} from "@phosphor-icons/react";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
export default function ChatWindowHeader({
|
|
||||||
sessionId,
|
|
||||||
settings = {},
|
|
||||||
iconUrl = null,
|
|
||||||
closeChat,
|
|
||||||
setChatHistory,
|
|
||||||
}) {
|
|
||||||
const [showingOptions, setShowOptions] = useState(false);
|
|
||||||
const menuRef = useRef();
|
|
||||||
const buttonRef = useRef();
|
|
||||||
|
|
||||||
const handleChatReset = async () => {
|
|
||||||
await ChatService.resetEmbedChatSession(settings, sessionId);
|
|
||||||
setChatHistory([]);
|
|
||||||
setShowOptions(false);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
function handleClickOutside(event) {
|
|
||||||
if (
|
|
||||||
menuRef.current &&
|
|
||||||
!menuRef.current.contains(event.target) &&
|
|
||||||
buttonRef.current &&
|
|
||||||
!buttonRef.current.contains(event.target)
|
|
||||||
) {
|
|
||||||
setShowOptions(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [menuRef]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{ borderBottom: "1px solid #E9E9E9" }}
|
|
||||||
className="allm-flex allm-items-center allm-relative allm-rounded-t-2xl"
|
|
||||||
id="anything-llm-header"
|
|
||||||
>
|
|
||||||
<div className="allm-flex allm-justify-center allm-items-center allm-w-full allm-h-[76px]">
|
|
||||||
<img
|
|
||||||
style={{ maxWidth: 48, maxHeight: 48 }}
|
|
||||||
src={iconUrl ?? AnythingLLMIcon}
|
|
||||||
alt={iconUrl ? "Brand" : "AnythingLLM Logo"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="allm-absolute allm-right-0 allm-flex allm-gap-x-1 allm-items-center allm-px-[22px]">
|
|
||||||
{settings.loaded && (
|
|
||||||
<button
|
|
||||||
ref={buttonRef}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowOptions(!showingOptions)}
|
|
||||||
className="allm-bg-transparent hover:allm-cursor-pointer allm-border-none hover:allm-bg-gray-100 allm-rounded-sm allm-text-slate-800/60"
|
|
||||||
aria-label="Options"
|
|
||||||
>
|
|
||||||
<DotsThreeOutlineVertical size={20} weight="fill" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={closeChat}
|
|
||||||
className="allm-bg-transparent hover:allm-cursor-pointer allm-border-none hover:allm-bg-gray-100 allm-rounded-sm allm-text-slate-800/60"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<X size={20} weight="bold" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<OptionsMenu
|
|
||||||
settings={settings}
|
|
||||||
showing={showingOptions}
|
|
||||||
resetChat={handleChatReset}
|
|
||||||
sessionId={sessionId}
|
|
||||||
menuRef={menuRef}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OptionsMenu({ settings, showing, resetChat, sessionId, menuRef }) {
|
|
||||||
if (!showing) return null;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={menuRef}
|
|
||||||
className="allm-bg-white allm-absolute allm-z-10 allm-flex allm-flex-col allm-gap-y-1 allm-rounded-xl allm-shadow-lg allm-top-[64px] allm-right-[46px]"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={resetChat}
|
|
||||||
className="hover:allm-cursor-pointer allm-bg-white allm-gap-x-[12px] hover:allm-bg-gray-100 allm-rounded-lg allm-border-none allm-flex allm-items-center allm-text-base allm-text-[#7A7D7E] allm-font-bold allm-px-4"
|
|
||||||
>
|
|
||||||
<ArrowCounterClockwise size={24} />
|
|
||||||
<p className="allm-text-[14px]">Reset Chat</p>
|
|
||||||
</button>
|
|
||||||
<ContactSupport email={settings.supportEmail} />
|
|
||||||
<SessionID sessionId={sessionId} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SessionID({ sessionId }) {
|
|
||||||
if (!sessionId) return null;
|
|
||||||
|
|
||||||
const [sessionIdCopied, setSessionIdCopied] = useState(false);
|
|
||||||
|
|
||||||
const copySessionId = () => {
|
|
||||||
navigator.clipboard.writeText(sessionId);
|
|
||||||
setSessionIdCopied(true);
|
|
||||||
setTimeout(() => setSessionIdCopied(false), 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sessionIdCopied) {
|
|
||||||
return (
|
|
||||||
<div className="hover:allm-cursor-pointer allm-bg-white allm-gap-x-[12px] hover:allm-bg-gray-100 allm-rounded-lg allm-border-none allm-flex allm-items-center allm-text-base allm-text-[#7A7D7E] allm-font-bold allm-px-4">
|
|
||||||
<Check size={24} />
|
|
||||||
<p className="allm-text-[14px] allm-font-sans">Copied!</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={copySessionId}
|
|
||||||
className="hover:allm-cursor-pointer allm-bg-white allm-gap-x-[12px] hover:allm-bg-gray-100 allm-rounded-lg allm-border-none allm-flex allm-items-center allm-text-base allm-text-[#7A7D7E] allm-font-bold allm-px-4"
|
|
||||||
>
|
|
||||||
<Copy size={24} />
|
|
||||||
<p className="allm-text-[14px]">Session ID</p>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContactSupport({ email = null }) {
|
|
||||||
if (!email) return null;
|
|
||||||
|
|
||||||
const subject = `Inquiry from ${window.location.origin}`;
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={`mailto:${email}?Subject=${encodeURIComponent(subject)}`}
|
|
||||||
className="allm-no-underline hover:allm-underline hover:allm-cursor-pointer allm-bg-white allm-gap-x-[12px] hover:allm-bg-gray-100 allm-rounded-lg allm-border-none allm-flex allm-items-center allm-text-base allm-text-[#7A7D7E] allm-font-bold allm-px-4"
|
|
||||||
>
|
|
||||||
<Envelope size={24} />
|
|
||||||
<p className="allm-text-[14px] allm-font-sans">Email Support</p>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
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";
|
|
||||||
import ResetChat from "../ResetChat";
|
|
||||||
|
|
||||||
export default function ChatWindow({ closeChat, settings, sessionId }) {
|
|
||||||
const { chatHistory, setChatHistory, loading } = useChatHistory(
|
|
||||||
settings,
|
|
||||||
sessionId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="allm-flex allm-flex-col allm-h-full">
|
|
||||||
<ChatWindowHeader
|
|
||||||
sessionId={sessionId}
|
|
||||||
settings={settings}
|
|
||||||
iconUrl={settings.brandImageUrl}
|
|
||||||
closeChat={closeChat}
|
|
||||||
setChatHistory={setChatHistory}
|
|
||||||
/>
|
|
||||||
<ChatHistoryLoading />
|
|
||||||
<div className="allm-pt-4 allm-pb-2 allm-h-fit allm-gap-y-1">
|
|
||||||
<SessionId />
|
|
||||||
<Sponsor settings={settings} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setEventDelegatorForCodeSnippets();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-flex allm-flex-col allm-h-full">
|
|
||||||
<ChatWindowHeader
|
|
||||||
sessionId={sessionId}
|
|
||||||
settings={settings}
|
|
||||||
iconUrl={settings.brandImageUrl}
|
|
||||||
closeChat={closeChat}
|
|
||||||
setChatHistory={setChatHistory}
|
|
||||||
/>
|
|
||||||
<div className="allm-flex-grow allm-overflow-y-auto">
|
|
||||||
<ChatContainer
|
|
||||||
sessionId={sessionId}
|
|
||||||
settings={settings}
|
|
||||||
knownHistory={chatHistory}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="allm-mt-4 allm-pb-4 allm-h-fit allm-gap-y-2 allm-z-10">
|
|
||||||
<Sponsor settings={settings} />
|
|
||||||
<ResetChat
|
|
||||||
setChatHistory={setChatHistory}
|
|
||||||
settings={settings}
|
|
||||||
sessionId={sessionId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enables us to safely markdown and sanitize all responses without risk of injection
|
|
||||||
// but still be able to attach a handler to copy code snippets on all elements
|
|
||||||
// that are code snippets.
|
|
||||||
function copyCodeSnippet(uuid) {
|
|
||||||
const target = document.querySelector(`[data-code="${uuid}"]`);
|
|
||||||
if (!target) return false;
|
|
||||||
|
|
||||||
const markdown =
|
|
||||||
target.parentElement?.parentElement?.querySelector(
|
|
||||||
"pre:first-of-type"
|
|
||||||
)?.innerText;
|
|
||||||
if (!markdown) return false;
|
|
||||||
|
|
||||||
window.navigator.clipboard.writeText(markdown);
|
|
||||||
|
|
||||||
target.classList.add("allm-text-green-500");
|
|
||||||
const originalText = target.innerHTML;
|
|
||||||
target.innerText = "Copied!";
|
|
||||||
target.setAttribute("disabled", true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
target.classList.remove("allm-text-green-500");
|
|
||||||
target.innerHTML = originalText;
|
|
||||||
target.removeAttribute("disabled");
|
|
||||||
}, 2500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listens and hunts for all data-code-snippet clicks.
|
|
||||||
function setEventDelegatorForCodeSnippets() {
|
|
||||||
document?.addEventListener("click", function (e) {
|
|
||||||
const target = e.target.closest("[data-code-snippet]");
|
|
||||||
const uuidCode = target?.dataset?.code;
|
|
||||||
if (!uuidCode) return false;
|
|
||||||
copyCodeSnippet(uuidCode);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
import { embedderSettings } from "@/main";
|
|
||||||
|
|
||||||
const hljsCss = `
|
|
||||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
|
||||||
Theme: GitHub Dark Dimmed
|
|
||||||
Description: Dark dimmed theme as seen on github.com
|
|
||||||
Author: github.com
|
|
||||||
Maintainer: @Hirse
|
|
||||||
Updated: 2021-05-15
|
|
||||||
|
|
||||||
Colors taken from GitHub's CSS
|
|
||||||
*/.hljs{color:#adbac7;background:#22272e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const customCss = `
|
|
||||||
/**
|
|
||||||
* ==============================================
|
|
||||||
* Dot Falling
|
|
||||||
* ==============================================
|
|
||||||
*/
|
|
||||||
.allm-dot-falling {
|
|
||||||
position: relative;
|
|
||||||
left: -9999px;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #5fa4fa;
|
|
||||||
box-shadow: 9999px 0 0 0 #000000;
|
|
||||||
animation: dot-falling 1.5s infinite linear;
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allm-dot-falling::before,
|
|
||||||
.allm-dot-falling::after {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allm-dot-falling::before {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #000000;
|
|
||||||
animation: dot-falling-before 1.5s infinite linear;
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.allm-dot-falling::after {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #000000;
|
|
||||||
animation: dot-falling-after 1.5s infinite linear;
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes dot-falling {
|
|
||||||
0% {
|
|
||||||
box-shadow: 9999px -15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
25%,
|
|
||||||
50%,
|
|
||||||
75% {
|
|
||||||
box-shadow: 9999px 0 0 0 #000000;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 9999px 15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes dot-falling-before {
|
|
||||||
0% {
|
|
||||||
box-shadow: 9984px -15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
25%,
|
|
||||||
50%,
|
|
||||||
75% {
|
|
||||||
box-shadow: 9984px 0 0 0 #000000;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 9984px 15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes dot-falling-after {
|
|
||||||
0% {
|
|
||||||
box-shadow: 10014px -15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
25%,
|
|
||||||
50%,
|
|
||||||
75% {
|
|
||||||
box-shadow: 10014px 0 0 0 #000000;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 10014px 15px 0 0 rgba(152, 128, 255, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#chat-history::-webkit-scrollbar,
|
|
||||||
#chat-container::-webkit-scrollbar,
|
|
||||||
.allm-no-scroll::-webkit-scrollbar {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide scrollbar for IE, Edge and Firefox */
|
|
||||||
#chat-history,
|
|
||||||
#chat-container,
|
|
||||||
.allm-no-scroll {
|
|
||||||
-ms-overflow-style: none !important; /* IE and Edge */
|
|
||||||
scrollbar-width: none !important; /* Firefox */
|
|
||||||
}
|
|
||||||
|
|
||||||
span.allm-whitespace-pre-line>p {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function Head() {
|
|
||||||
return (
|
|
||||||
<head>
|
|
||||||
<style>{hljsCss}</style>
|
|
||||||
<style>{customCss}</style>
|
|
||||||
<link rel="stylesheet" href={embedderSettings.stylesSrc} />
|
|
||||||
</head>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import {
|
|
||||||
Plus,
|
|
||||||
ChatCircleDots,
|
|
||||||
Headset,
|
|
||||||
Binoculars,
|
|
||||||
MagnifyingGlass,
|
|
||||||
MagicWand,
|
|
||||||
} from "@phosphor-icons/react";
|
|
||||||
|
|
||||||
const CHAT_ICONS = {
|
|
||||||
plus: Plus,
|
|
||||||
chatBubble: ChatCircleDots,
|
|
||||||
support: Headset,
|
|
||||||
search2: Binoculars,
|
|
||||||
search: MagnifyingGlass,
|
|
||||||
magic: MagicWand,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function OpenButton({ settings, isOpen, toggleOpen }) {
|
|
||||||
if (isOpen) return null;
|
|
||||||
const ChatIcon = CHAT_ICONS.hasOwnProperty(settings?.chatIcon)
|
|
||||||
? CHAT_ICONS[settings.chatIcon]
|
|
||||||
: CHAT_ICONS.plus;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
style={{ backgroundColor: settings.buttonColor }}
|
|
||||||
id="anything-llm-embed-chat-button"
|
|
||||||
onClick={toggleOpen}
|
|
||||||
className={`hover:allm-cursor-pointer allm-border-none allm-flex allm-items-center allm-justify-center allm-p-4 allm-rounded-full allm-text-white allm-text-2xl hover:allm-opacity-95`}
|
|
||||||
aria-label="Toggle Menu"
|
|
||||||
>
|
|
||||||
<ChatIcon className="text-white" />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import ChatService from "@/models/chatService";
|
|
||||||
|
|
||||||
export default function ResetChat({ setChatHistory, settings, sessionId }) {
|
|
||||||
const handleChatReset = async () => {
|
|
||||||
await ChatService.resetEmbedChatSession(settings, sessionId);
|
|
||||||
setChatHistory([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-w-full allm-flex allm-justify-center">
|
|
||||||
<button
|
|
||||||
style={{ color: "#7A7D7E" }}
|
|
||||||
className="hover:allm-cursor-pointer allm-border-none allm-text-sm allm-bg-transparent hover:allm-opacity-80 hover:allm-underline"
|
|
||||||
onClick={() => handleChatReset()}
|
|
||||||
>
|
|
||||||
Reset Chat
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import useSessionId from "@/hooks/useSessionId";
|
|
||||||
|
|
||||||
export default function SessionId() {
|
|
||||||
const sessionId = useSessionId();
|
|
||||||
if (!sessionId) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-text-xs allm-text-gray-300 allm-w-full allm-text-center">
|
|
||||||
{sessionId}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
export default function Sponsor({ settings }) {
|
|
||||||
if (!!settings.noSponsor) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="allm-flex allm-w-full allm-items-center allm-justify-center">
|
|
||||||
<a
|
|
||||||
style={{ color: "#0119D9" }}
|
|
||||||
href={settings.sponsorLink ?? "#"}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="allm-text-xs allm-font-sans hover:allm-opacity-80 hover:allm-underline"
|
|
||||||
>
|
|
||||||
{settings.sponsorText}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import ChatService from "@/models/chatService";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function useChatHistory(settings = null, sessionId = null) {
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [messages, setMessages] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchChatHistory() {
|
|
||||||
if (!sessionId || !settings) return;
|
|
||||||
try {
|
|
||||||
const formattedMessages = await ChatService.embedSessionHistory(
|
|
||||||
settings,
|
|
||||||
sessionId
|
|
||||||
);
|
|
||||||
setMessages(formattedMessages);
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching historical chats:", error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchChatHistory();
|
|
||||||
}, [sessionId, settings]);
|
|
||||||
|
|
||||||
return { chatHistory: messages, setChatHistory: setMessages, loading };
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { CHAT_UI_REOPEN } from "@/utils/constants";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function useOpenChat() {
|
|
||||||
const [isOpen, setOpen] = useState(
|
|
||||||
!!window?.localStorage?.getItem(CHAT_UI_REOPEN) || false
|
|
||||||
);
|
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { embedderSettings } from "../main";
|
|
||||||
|
|
||||||
const DEFAULT_SETTINGS = {
|
|
||||||
embedId: null, //required
|
|
||||||
baseApiUrl: null, // required
|
|
||||||
|
|
||||||
// Override properties that can be defined.
|
|
||||||
prompt: null, // override
|
|
||||||
model: null, // override
|
|
||||||
temperature: null, //override
|
|
||||||
|
|
||||||
// style parameters
|
|
||||||
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://anythingllm.com", // default sponsor link
|
|
||||||
position: "bottom-right", // position of chat button/window
|
|
||||||
assistantName: "AnythingLLM Chat Assistant", // default assistant name
|
|
||||||
assistantIcon: null, // default assistant icon
|
|
||||||
windowHeight: null, // height of chat window in number:css-prefix
|
|
||||||
windowWidth: null, // width of chat window in number:css-prefix
|
|
||||||
textSize: null, // text size in px (number only)
|
|
||||||
|
|
||||||
// behaviors
|
|
||||||
openOnLoad: "off", // or "on"
|
|
||||||
supportEmail: null, // string of email for contact
|
|
||||||
username: null, // The display or readable name set on a script
|
|
||||||
defaultMessages: [], // list of strings for default messages.
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function useGetScriptAttributes() {
|
|
||||||
const [settings, setSettings] = useState({
|
|
||||||
loaded: false,
|
|
||||||
...DEFAULT_SETTINGS,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function fetchAttribs() {
|
|
||||||
if (!document) return false;
|
|
||||||
if (
|
|
||||||
!embedderSettings.settings.baseApiUrl ||
|
|
||||||
!embedderSettings.settings.embedId
|
|
||||||
)
|
|
||||||
throw new Error(
|
|
||||||
"[AnythingLLM Embed Module::Abort] - Invalid script tag setup detected. Missing required parameters for boot!"
|
|
||||||
);
|
|
||||||
|
|
||||||
setSettings({
|
|
||||||
...DEFAULT_SETTINGS,
|
|
||||||
...parseAndValidateEmbedSettings(embedderSettings.settings),
|
|
||||||
loaded: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fetchAttribs();
|
|
||||||
}, [document]);
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validations = {
|
|
||||||
_fallbacks: {
|
|
||||||
defaultMessages: [],
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultMessages: function (value = null) {
|
|
||||||
if (typeof value !== "string") return this._fallbacks.defaultMessages;
|
|
||||||
try {
|
|
||||||
const list = value.split(",");
|
|
||||||
if (
|
|
||||||
!Array.isArray(list) ||
|
|
||||||
list.length === 0 ||
|
|
||||||
!list.every((v) => typeof v === "string" && v.length > 0)
|
|
||||||
)
|
|
||||||
throw new Error(
|
|
||||||
"Invalid default-messages attribute value. Must be array of strings"
|
|
||||||
);
|
|
||||||
return list.map((v) => v.trim());
|
|
||||||
} catch (e) {
|
|
||||||
console.error("AnythingLLMEmbed", e);
|
|
||||||
return this._fallbacks.defaultMessages;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function parseAndValidateEmbedSettings(settings = {}) {
|
|
||||||
const validated = {};
|
|
||||||
for (let [key, value] of Object.entries(settings)) {
|
|
||||||
if (!validations.hasOwnProperty(key)) {
|
|
||||||
validated[key] = value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatedValue = validations[key](value);
|
|
||||||
validated[key] = validatedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validated;
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { embedderSettings } from "../main";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
export default function useSessionId() {
|
|
||||||
const [sessionId, setSessionId] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function getOrAssignSessionId() {
|
|
||||||
if (!window || !embedderSettings?.settings?.embedId) return;
|
|
||||||
|
|
||||||
const STORAGE_IDENTIFIER = `allm_${embedderSettings?.settings?.embedId}_session_id`;
|
|
||||||
const currentId = window.localStorage.getItem(STORAGE_IDENTIFIER);
|
|
||||||
if (!!currentId) {
|
|
||||||
console.log(`Resuming session id`, currentId);
|
|
||||||
setSessionId(currentId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newId = v4();
|
|
||||||
console.log(`Registering new session id`, newId);
|
|
||||||
window.localStorage.setItem(STORAGE_IDENTIFIER, newId);
|
|
||||||
setSessionId(newId);
|
|
||||||
}
|
|
||||||
getOrAssignSessionId();
|
|
||||||
}, [window]);
|
|
||||||
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
.msg-suggestion {
|
|
||||||
animation-name: fadeIn;
|
|
||||||
animation-duration: 300ms;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
0% {
|
|
||||||
opacity: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
25% {
|
|
||||||
opacity: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
opacity: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
75% {
|
|
||||||
opacity: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import App from "./App.jsx";
|
|
||||||
import "./index.css";
|
|
||||||
import { parseStylesSrc } from "./utils/constants.js";
|
|
||||||
const appElement = document.createElement("div");
|
|
||||||
|
|
||||||
document.body.appendChild(appElement);
|
|
||||||
const root = ReactDOM.createRoot(appElement);
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
|
|
||||||
const scriptSettings = Object.assign(
|
|
||||||
{},
|
|
||||||
document?.currentScript?.dataset || {}
|
|
||||||
);
|
|
||||||
export const embedderSettings = {
|
|
||||||
settings: scriptSettings,
|
|
||||||
stylesSrc: parseStylesSrc(document?.currentScript?.src),
|
|
||||||
USER_STYLES: {
|
|
||||||
msgBg: scriptSettings?.userBgColor ?? "#3DBEF5",
|
|
||||||
base: `allm-text-white allm-rounded-t-[18px] allm-rounded-bl-[18px] allm-rounded-br-[4px] allm-mx-[20px]`,
|
|
||||||
},
|
|
||||||
ASSISTANT_STYLES: {
|
|
||||||
msgBg: scriptSettings?.assistantBgColor ?? "#FFFFFF",
|
|
||||||
base: `allm-text-[#222628] allm-rounded-t-[18px] allm-rounded-br-[18px] allm-rounded-bl-[4px] allm-mr-[37px] allm-ml-[9px]`,
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,109 +0,0 @@
|
|||||||
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
const ChatService = {
|
|
||||||
embedSessionHistory: async function (embedSettings, sessionId) {
|
|
||||||
const { embedId, baseApiUrl } = embedSettings;
|
|
||||||
return await fetch(`${baseApiUrl}/${embedId}/${sessionId}`)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.ok) return res.json();
|
|
||||||
throw new Error("Invalid response from server");
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
return res.history.map((msg) => ({
|
|
||||||
...msg,
|
|
||||||
id: v4(),
|
|
||||||
sender: msg.role === "user" ? "user" : "system",
|
|
||||||
textResponse: msg.content,
|
|
||||||
close: false,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
resetEmbedChatSession: async function (embedSettings, sessionId) {
|
|
||||||
const { baseApiUrl, embedId } = embedSettings;
|
|
||||||
return await fetch(`${baseApiUrl}/${embedId}/${sessionId}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
})
|
|
||||||
.then((res) => res.ok)
|
|
||||||
.catch(() => false);
|
|
||||||
},
|
|
||||||
streamChat: async function (sessionId, embedSettings, message, handleChat) {
|
|
||||||
const { baseApiUrl, embedId, username } = embedSettings;
|
|
||||||
const overrides = {
|
|
||||||
prompt: embedSettings?.prompt ?? null,
|
|
||||||
model: embedSettings?.model ?? null,
|
|
||||||
temperature: embedSettings?.temperature ?? null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ctrl = new AbortController();
|
|
||||||
await fetchEventSource(`${baseApiUrl}/${embedId}/stream-chat`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
message,
|
|
||||||
sessionId,
|
|
||||||
username,
|
|
||||||
...overrides,
|
|
||||||
}),
|
|
||||||
signal: ctrl.signal,
|
|
||||||
openWhenHidden: true,
|
|
||||||
async onopen(response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return; // everything's good
|
|
||||||
} else if (response.status >= 400) {
|
|
||||||
await response
|
|
||||||
.json()
|
|
||||||
.then((serverResponse) => {
|
|
||||||
handleChat(serverResponse);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
handleChat({
|
|
||||||
id: v4(),
|
|
||||||
type: "abort",
|
|
||||||
textResponse: null,
|
|
||||||
sources: [],
|
|
||||||
close: true,
|
|
||||||
error: `An error occurred while streaming response. Code ${response.status}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ctrl.abort();
|
|
||||||
throw new Error();
|
|
||||||
} else {
|
|
||||||
handleChat({
|
|
||||||
id: v4(),
|
|
||||||
type: "abort",
|
|
||||||
textResponse: null,
|
|
||||||
sources: [],
|
|
||||||
close: true,
|
|
||||||
error: `An error occurred while streaming response. Unknown Error.`,
|
|
||||||
});
|
|
||||||
ctrl.abort();
|
|
||||||
throw new Error("Unknown Error");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async onmessage(msg) {
|
|
||||||
try {
|
|
||||||
const chatResult = JSON.parse(msg.data);
|
|
||||||
handleChat(chatResult);
|
|
||||||
} catch {}
|
|
||||||
},
|
|
||||||
onerror(err) {
|
|
||||||
handleChat({
|
|
||||||
id: v4(),
|
|
||||||
type: "abort",
|
|
||||||
textResponse: null,
|
|
||||||
sources: [],
|
|
||||||
close: true,
|
|
||||||
error: `An error occurred while streaming response. ${err.message}`,
|
|
||||||
});
|
|
||||||
ctrl.abort();
|
|
||||||
throw new Error();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChatService;
|
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
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,96 +0,0 @@
|
|||||||
// For handling of synchronous chats that are not utilizing streaming or chat requests.
|
|
||||||
export default function handleChat(
|
|
||||||
chatResult,
|
|
||||||
setLoadingResponse,
|
|
||||||
setChatHistory,
|
|
||||||
remHistory,
|
|
||||||
_chatHistory
|
|
||||||
) {
|
|
||||||
const { uuid, textResponse, type, sources = [], error, close } = chatResult;
|
|
||||||
|
|
||||||
if (type === "abort") {
|
|
||||||
setLoadingResponse(false);
|
|
||||||
setChatHistory([
|
|
||||||
...remHistory,
|
|
||||||
{
|
|
||||||
uuid,
|
|
||||||
content: textResponse,
|
|
||||||
role: "assistant",
|
|
||||||
sources,
|
|
||||||
closed: true,
|
|
||||||
error,
|
|
||||||
animate: false,
|
|
||||||
pending: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
_chatHistory.push({
|
|
||||||
uuid,
|
|
||||||
content: textResponse,
|
|
||||||
role: "assistant",
|
|
||||||
sources,
|
|
||||||
closed: true,
|
|
||||||
error,
|
|
||||||
animate: false,
|
|
||||||
pending: false,
|
|
||||||
});
|
|
||||||
} else if (type === "textResponse") {
|
|
||||||
setLoadingResponse(false);
|
|
||||||
setChatHistory([
|
|
||||||
...remHistory,
|
|
||||||
{
|
|
||||||
uuid,
|
|
||||||
content: textResponse,
|
|
||||||
role: "assistant",
|
|
||||||
sources,
|
|
||||||
closed: close,
|
|
||||||
error,
|
|
||||||
animate: !close,
|
|
||||||
pending: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
_chatHistory.push({
|
|
||||||
uuid,
|
|
||||||
content: textResponse,
|
|
||||||
role: "assistant",
|
|
||||||
sources,
|
|
||||||
closed: close,
|
|
||||||
error,
|
|
||||||
animate: !close,
|
|
||||||
pending: false,
|
|
||||||
});
|
|
||||||
} else if (type === "textResponseChunk") {
|
|
||||||
const chatIdx = _chatHistory.findIndex((chat) => chat.uuid === uuid);
|
|
||||||
if (chatIdx !== -1) {
|
|
||||||
const existingHistory = { ..._chatHistory[chatIdx] };
|
|
||||||
const updatedHistory = {
|
|
||||||
...existingHistory,
|
|
||||||
content: existingHistory.content + textResponse,
|
|
||||||
sources,
|
|
||||||
error,
|
|
||||||
closed: close,
|
|
||||||
animate: !close,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
_chatHistory[chatIdx] = updatedHistory;
|
|
||||||
} else {
|
|
||||||
_chatHistory.push({
|
|
||||||
uuid,
|
|
||||||
sources,
|
|
||||||
error,
|
|
||||||
content: textResponse,
|
|
||||||
role: "assistant",
|
|
||||||
closed: close,
|
|
||||||
animate: !close,
|
|
||||||
pending: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setChatHistory([..._chatHistory]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chatPrompt(workspace) {
|
|
||||||
return (
|
|
||||||
workspace?.openAiPrompt ??
|
|
||||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed."
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import { encode as HTMLEncode } from "he";
|
|
||||||
import markdownIt from "markdown-it";
|
|
||||||
import { staticHljs as hljs } from "./hljs";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
const markdown = markdownIt({
|
|
||||||
html: false,
|
|
||||||
typographer: true,
|
|
||||||
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">
|
|
||||||
<div class="flex gap-2"><code class="text-xs"></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">` +
|
|
||||||
HTMLEncode(code) +
|
|
||||||
"</pre></div>"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// Enable <ol> and <ul> items to not assume an HTML structure so we can keep numbering from responses.
|
|
||||||
.disable("list");
|
|
||||||
|
|
||||||
export default function renderMarkdown(text = "") {
|
|
||||||
return markdown.render(text);
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
export const CHAT_UI_REOPEN = "___anythingllm-chat-widget-open___";
|
|
||||||
export function parseStylesSrc(scriptSrc = null) {
|
|
||||||
try {
|
|
||||||
const _url = new URL(scriptSrc);
|
|
||||||
_url.pathname = _url.pathname
|
|
||||||
.replace("anythingllm-chat-widget.js", "anythingllm-chat-widget.min.css")
|
|
||||||
.replace(
|
|
||||||
"anythingllm-chat-widget.min.js",
|
|
||||||
"anythingllm-chat-widget.min.css"
|
|
||||||
);
|
|
||||||
return _url.toString();
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
export function formatDate(sentAt) {
|
|
||||||
const date = new Date(sentAt * 1000);
|
|
||||||
const timeString = date.toLocaleTimeString([], {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: true,
|
|
||||||
});
|
|
||||||
return timeString;
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
darkMode: 'false',
|
|
||||||
prefix: 'allm-',
|
|
||||||
corePlugins: {
|
|
||||||
preflight: false,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
relative: true,
|
|
||||||
files: [
|
|
||||||
"./src/components/**/*.{js,jsx}",
|
|
||||||
"./src/hooks/**/*.js",
|
|
||||||
"./src/models/**/*.js",
|
|
||||||
"./src/pages/**/*.{js,jsx}",
|
|
||||||
"./src/utils/**/*.js",
|
|
||||||
"./src/*.jsx",
|
|
||||||
"./index.html",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
rotate: {
|
|
||||||
"270": "270deg",
|
|
||||||
"360": "360deg"
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
"black-900": "#141414",
|
|
||||||
accent: "#3D4147",
|
|
||||||
"sidebar-button": "#31353A",
|
|
||||||
sidebar: "#25272C",
|
|
||||||
"historical-msg-system": "rgba(255, 255, 255, 0.05);",
|
|
||||||
"historical-msg-user": "#2C2F35",
|
|
||||||
outline: "#4E5153",
|
|
||||||
"primary-button": "#46C8FF",
|
|
||||||
secondary: "#2C2F36",
|
|
||||||
"dark-input": "#18181B",
|
|
||||||
"mobile-onboarding": "#2C2F35",
|
|
||||||
"dark-highlight": "#1C1E21",
|
|
||||||
"dark-text": "#222628",
|
|
||||||
description: "#D2D5DB",
|
|
||||||
"x-button": "#9CA3AF"
|
|
||||||
},
|
|
||||||
backgroundImage: {
|
|
||||||
"preference-gradient":
|
|
||||||
"linear-gradient(180deg, #5A5C63 0%, rgba(90, 92, 99, 0.28) 100%);",
|
|
||||||
"chat-msg-user-gradient":
|
|
||||||
"linear-gradient(180deg, #3D4147 0%, #2C2F35 100%);",
|
|
||||||
"selected-preference-gradient":
|
|
||||||
"linear-gradient(180deg, #313236 0%, rgba(63.40, 64.90, 70.13, 0) 100%);",
|
|
||||||
"main-gradient": "linear-gradient(180deg, #3D4147 0%, #2C2F35 100%)",
|
|
||||||
"modal-gradient": "linear-gradient(180deg, #3D4147 0%, #2C2F35 100%)",
|
|
||||||
"sidebar-gradient": "linear-gradient(90deg, #5B616A 0%, #3F434B 100%)",
|
|
||||||
"login-gradient": "linear-gradient(180deg, #3D4147 0%, #2C2F35 100%)",
|
|
||||||
"menu-item-gradient":
|
|
||||||
"linear-gradient(90deg, #3D4147 0%, #2C2F35 100%)",
|
|
||||||
"menu-item-selected-gradient":
|
|
||||||
"linear-gradient(90deg, #5B616A 0%, #3F434B 100%)",
|
|
||||||
"workspace-item-gradient":
|
|
||||||
"linear-gradient(90deg, #3D4147 0%, #2C2F35 100%)",
|
|
||||||
"workspace-item-selected-gradient":
|
|
||||||
"linear-gradient(90deg, #5B616A 0%, #3F434B 100%)",
|
|
||||||
"switch-selected": "linear-gradient(146deg, #5B616A 0%, #3F434B 100%)"
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
sans: [
|
|
||||||
"plus-jakarta-sans",
|
|
||||||
"ui-sans-serif",
|
|
||||||
"system-ui",
|
|
||||||
"-apple-system",
|
|
||||||
"BlinkMacSystemFont",
|
|
||||||
'"Segoe UI"',
|
|
||||||
"Roboto",
|
|
||||||
'"Helvetica Neue"',
|
|
||||||
"Arial",
|
|
||||||
'"Noto Sans"',
|
|
||||||
"sans-serif",
|
|
||||||
'"Apple Color Emoji"',
|
|
||||||
'"Segoe UI Emoji"',
|
|
||||||
'"Segoe UI Symbol"',
|
|
||||||
'"Noto Color Emoji"'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
sweep: "sweep 0.5s ease-in-out"
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
sweep: {
|
|
||||||
"0%": { transform: "scaleX(0)", transformOrigin: "bottom left" },
|
|
||||||
"100%": { transform: "scaleX(1)", transformOrigin: "bottom left" }
|
|
||||||
},
|
|
||||||
fadeIn: {
|
|
||||||
"0%": { opacity: 0 },
|
|
||||||
"100%": { opacity: 1 }
|
|
||||||
},
|
|
||||||
fadeOut: {
|
|
||||||
"0%": { opacity: 1 },
|
|
||||||
"100%": { opacity: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: []
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
// vite.config.js
|
|
||||||
import { defineConfig } from "vite"
|
|
||||||
import { fileURLToPath, URL } from "url"
|
|
||||||
import postcss from "./postcss.config.js"
|
|
||||||
import react from "@vitejs/plugin-react"
|
|
||||||
import image from "@rollup/plugin-image"
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react(), image()],
|
|
||||||
define: {
|
|
||||||
// In dev, we need to disable this, but in prod, we need to enable it
|
|
||||||
"process.env.NODE_ENV": JSON.stringify("production")
|
|
||||||
},
|
|
||||||
css: {
|
|
||||||
postcss
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: [
|
|
||||||
{
|
|
||||||
find: "@",
|
|
||||||
replacement: fileURLToPath(new URL("./src", import.meta.url))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
process: "process/browser",
|
|
||||||
stream: "stream-browserify",
|
|
||||||
zlib: "browserify-zlib",
|
|
||||||
util: "util",
|
|
||||||
find: /^~.+/,
|
|
||||||
replacement: (val) => {
|
|
||||||
return val.replace(/^~/, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
lib: {
|
|
||||||
entry: "src/main.jsx",
|
|
||||||
name: "EmbeddedAnythingLLM",
|
|
||||||
formats: ["umd"],
|
|
||||||
fileName: (_format) => `anythingllm-chat-widget.js`
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: [
|
|
||||||
// Reduces transformation time by 50% and we don't even use this variant, so we can ignore.
|
|
||||||
/@phosphor-icons\/react\/dist\/ssr/
|
|
||||||
]
|
|
||||||
},
|
|
||||||
commonjsOptions: {
|
|
||||||
transformMixedEsModules: true
|
|
||||||
},
|
|
||||||
cssCodeSplit: false,
|
|
||||||
assetsInlineLimit: 100000000,
|
|
||||||
minify: "esbuild",
|
|
||||||
outDir: "dist",
|
|
||||||
emptyOutDir: true,
|
|
||||||
inlineDynamicImports: true,
|
|
||||||
assetsDir: "",
|
|
||||||
sourcemap: "inline"
|
|
||||||
},
|
|
||||||
optimizeDeps: {
|
|
||||||
esbuildOptions: {
|
|
||||||
define: {
|
|
||||||
global: "globalThis"
|
|
||||||
},
|
|
||||||
plugins: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
3430
embed/yarn.lock
3430
embed/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user