Move embed to submodule (#2163)

* Move `embed` to submodule

* update README
This commit is contained in:
Timothy Carambat 2024-08-22 15:31:36 -07:00 committed by GitHub
parent 2de9e492ec
commit 3a3399af94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 6 additions and 5673 deletions

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "embed"]
branch = main
path = embed
url = git@github.com:Mintplex-Labs/anythingllm-embed.git

View File

@ -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

@ -0,0 +1 @@
Subproject commit 22a0848d58e3a758d85d93d9204a72a65854ea94

25
embed/.gitignore vendored
View File

@ -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?

View File

@ -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_

View File

@ -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>

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"jsx": "react",
"paths": {
"@/*": ["./src/*"],
},
},
}

View File

@ -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"
}
}

View File

@ -1,10 +0,0 @@
import tailwind from 'tailwindcss'
import autoprefixer from 'autoprefixer'
import tailwindConfig from './tailwind.config.js'
export default {
plugins: [
tailwind(tailwindConfig),
autoprefixer,
],
}

View File

@ -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`)

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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);
});
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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 };
}

View File

@ -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 };
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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%;
}
}

View File

@ -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]`,
},
};

View File

@ -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;

View File

@ -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;

View File

@ -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."
);
}

View File

@ -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);
}

View File

@ -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 "";
}
}

View File

@ -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;
}

View File

@ -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: []
}

View File

@ -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: []
}
}
})

File diff suppressed because it is too large Load Diff