Merge branch 'master' of github.com:Mintplex-Labs/anything-llm into render

This commit is contained in:
timothycarambat 2024-04-12 14:57:46 -07:00
commit fde4e5400f
14 changed files with 172 additions and 100 deletions

View File

@ -13,7 +13,9 @@ class RepoLoader {
#validGithubUrl() { #validGithubUrl() {
const UrlPattern = require("url-pattern"); const UrlPattern = require("url-pattern");
const pattern = new UrlPattern("https\\://github.com/(:author)/(:project)"); const pattern = new UrlPattern(
"https\\://github.com/(:author)/(:project(*))"
);
const match = pattern.match(this.repo); const match = pattern.match(this.repo);
if (!match) return false; if (!match) return false;

View File

@ -21,7 +21,21 @@ class MimeDetector {
// which has had this extension far before TS was invented. So need to force re-map this MIME map. // which has had this extension far before TS was invented. So need to force re-map this MIME map.
this.lib.define( this.lib.define(
{ {
"text/plain": ["ts", "py", "opts", "lock", "jsonl", "qml", "sh"], "text/plain": [
"ts",
"py",
"opts",
"lock",
"jsonl",
"qml",
"sh",
"c",
"cs",
"h",
"js",
"lua",
"pas",
],
}, },
true true
); );

View File

@ -14,6 +14,7 @@ This folder of AnythingLLM contains the source code for how the embedded version
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. 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 ### Security
- Users will _not_ be able to view or read context snippets like they can in the core AnythingLLM application - 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. - 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. - **Recommended** You can limit both the number of chats an embedding can process **and** per-session.
@ -21,6 +22,7 @@ The AnythingLLM Embedded chat widget allows you to expose a workspace and its em
_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_ _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 ### Developer Setup
- `cd embed` from the root of the repo - `cd embed` from the root of the repo
- `yarn` to install all dev and script dependencies - `yarn` to install all dev and script dependencies
- `yarn dev` to boot up an example HTML page to use the chat embed widget. - `yarn dev` to boot up an example HTML page to use the chat embed widget.
@ -34,6 +36,7 @@ While in development mode (`yarn dev`) the script will rebuild on any changes to
### `<script>` tag HTML embed ### `<script>` tag HTML embed
The primary way of embedding a workspace as a chat widget is via a simple `<script>` The primary way of embedding a workspace as a chat widget is via a simple `<script>`
```html ```html
<!-- <!--
An example of a script tag embed An example of a script tag embed
@ -44,13 +47,14 @@ REQUIRED data attributes:
<script <script
data-embed-id="5fc05aaf-2f2c-4c84-87a3-367a4692c1ee" data-embed-id="5fc05aaf-2f2c-4c84-87a3-367a4692c1ee"
data-base-api-url="http://localhost:3001/api/embed" data-base-api-url="http://localhost:3001/api/embed"
src="http://localhost:3000/embed/anythingllm-chat-widget.min.js"> src="http://localhost:3000/embed/anythingllm-chat-widget.min.js"
</script> ></script>
``` ```
### `<script>` Customization Options ### `<script>` Customization Options
**LLM Overrides** **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-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-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.
@ -58,6 +62,7 @@ REQUIRED data attributes:
- `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. - `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** **Style Overrides**
- `data-chat-icon` — The chat bubble icon show when chat is closed. Options are `plus`, `chatCircle`, `support`, `search2`, `search`, `magic`. - `data-chat-icon` — The chat bubble icon show when chat is closed. Options are `plus`, `chatCircle`, `support`, `search2`, `search`, `magic`.
- `data-button-color` — The chat bubble background color shown when chat is closed. Value must be hex color code. - `data-button-color` — The chat bubble background color shown when chat is closed. Value must be hex color code.
@ -78,15 +83,20 @@ REQUIRED data attributes:
- `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-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.
**Behavior Overrides** **Behavior Overrides**
- `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user. - `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user.
- `data-support-email` — Shows a support email that the user can used to draft an email via the "three dot" menu in the top right. Option will not appear if it is not set. - `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 ### `<iframe>` tag HTML embed
_work in progress_ _work in progress_
### `<iframe>` Customization Options ### `<iframe>` Customization Options
_work in progress_
_work in progress_

View File

@ -19,7 +19,8 @@ const HistoricalMessage = forwardRef(
<div <div
className={`text-[10px] font-medium text-gray-400 ml-[54px] mr-6 mb-2 text-left`} className={`text-[10px] font-medium text-gray-400 ml-[54px] mr-6 mb-2 text-left`}
> >
AnythingLLM Chat Assistant {embedderSettings.settings.assistantName ||
"Anything LLM Chat Assistant"}
</div> </div>
)} )}
<div <div
@ -31,7 +32,7 @@ const HistoricalMessage = forwardRef(
> >
{role === "assistant" && ( {role === "assistant" && (
<img <img
src={AnythingLLMIcon} src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon" alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2 mt-2" className="w-9 h-9 flex-shrink-0 ml-2 mt-2"
id="anything-llm-icon" id="anything-llm-icon"

View File

@ -13,7 +13,7 @@ const PromptReply = forwardRef(
return ( return (
<div className={`flex items-start w-full h-fit justify-start`}> <div className={`flex items-start w-full h-fit justify-start`}>
<img <img
src={AnythingLLMIcon} src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon" alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2" className="w-9 h-9 flex-shrink-0 ml-2"
/> />
@ -33,7 +33,7 @@ const PromptReply = forwardRef(
return ( return (
<div className={`flex items-end w-full h-fit justify-start`}> <div className={`flex items-end w-full h-fit justify-start`}>
<img <img
src={AnythingLLMIcon} src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon" alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2" className="w-9 h-9 flex-shrink-0 ml-2"
/> />
@ -60,7 +60,8 @@ const PromptReply = forwardRef(
<div <div
className={`text-[10px] font-medium text-gray-400 ml-[54px] mr-6 mb-2 text-left`} className={`text-[10px] font-medium text-gray-400 ml-[54px] mr-6 mb-2 text-left`}
> >
AnythingLLM Chat Assistant {embedderSettings.settings.assistantName ||
"Anything LLM Chat Assistant"}
</div> </div>
<div <div
key={uuid} key={uuid}
@ -68,7 +69,7 @@ const PromptReply = forwardRef(
className={`flex items-start w-full h-fit justify-start`} className={`flex items-start w-full h-fit justify-start`}
> >
<img <img
src={AnythingLLMIcon} src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon" alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2" className="w-9 h-9 flex-shrink-0 ml-2"
/> />

View File

@ -21,6 +21,8 @@ const DEFAULT_SETTINGS = {
sponsorText: "Powered by AnythingLLM", // default sponsor text sponsorText: "Powered by AnythingLLM", // default sponsor text
sponsorLink: "https://useanything.com", // default sponsor link sponsorLink: "https://useanything.com", // default sponsor link
position: "bottom-right", // position of chat button/window position: "bottom-right", // position of chat button/window
assistantName: "AnythingLLM Chat Assistant", // default assistant name
assistantIcon: null, // default assistant icon
// behaviors // behaviors
openOnLoad: "off", // or "on" openOnLoad: "off", // or "on"

File diff suppressed because one or more lines are too long

View File

@ -173,6 +173,14 @@ const Workspace = {
return result; return result;
}, },
wipeVectorDb: async function (slug) {
return await fetch(`${API_BASE}/workspace/${slug}/reset-vector-db`, {
method: "DELETE",
headers: baseHeaders(),
})
.then((res) => res.ok)
.catch(() => false);
},
uploadFile: async function (slug, formData) { uploadFile: async function (slug, formData) {
const response = await fetch(`${API_BASE}/workspace/${slug}/upload`, { const response = await fetch(`${API_BASE}/workspace/${slug}/upload`, {
method: "POST", method: "POST",

View File

@ -8,7 +8,11 @@ const TITLE = "Welcome to AnythingLLM";
const DESCRIPTION = "Help us make AnythingLLM built for your needs. Optional."; const DESCRIPTION = "Help us make AnythingLLM built for your needs. Optional.";
async function sendQuestionnaire({ email, useCase, comment }) { async function sendQuestionnaire({ email, useCase, comment }) {
if (import.meta.env.DEV) return; if (import.meta.env.DEV) {
console.log("sendQuestionnaire", { email, useCase, comment });
return;
}
return fetch(`https://onboarding-wxich7363q-uc.a.run.app`, { return fetch(`https://onboarding-wxich7363q-uc.a.run.app`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
@ -38,9 +42,25 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
navigate(paths.onboarding.createWorkspace()); navigate(paths.onboarding.createWorkspace());
return; return;
} }
if (submitRef.current) {
submitRef.current.click(); if (!formRef.current) {
skipSurvey();
return;
} }
// Check if any inputs are not empty. If that is the case, trigger form validation.
// via the requestSubmit() handler
const formData = new FormData(formRef.current);
if (
!!formData.get("email") ||
!!formData.get("use_case") ||
!!formData.get("comment")
) {
formRef.current.requestSubmit();
return;
}
skipSurvey();
} }
function skipSurvey() { function skipSurvey() {
@ -116,26 +136,24 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
<div className="mt-2 gap-y-3 flex flex-col"> <div className="mt-2 gap-y-3 flex flex-col">
<label <label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${ className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "business" selectedOption === "job" ? "border-white border-opacity-40" : ""
? "border-white border-opacity-40"
: ""
} hover:border-white/60`} } hover:border-white/60`}
> >
<input <input
type="radio" type="radio"
name="use_case" name="use_case"
value={"business"} value={"job"}
checked={selectedOption === "business"} checked={selectedOption === "job"}
onChange={(e) => setSelectedOption(e.target.value)} onChange={(e) => setSelectedOption(e.target.value)}
className="hidden" className="hidden"
/> />
<div <div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${ className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "business" ? "bg-white" : "" selectedOption === "job" ? "bg-white" : ""
}`} }`}
></div> ></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight"> <div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my business For work
</div> </div>
</label> </label>
<label <label
@ -159,77 +177,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
}`} }`}
></div> ></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight"> <div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For personal use For my personal use
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "education"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"education"}
checked={selectedOption === "education"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "education" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my education
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "side_hustle"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"side_hustle"}
checked={selectedOption === "side_hustle"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "side_hustle" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my side-hustle
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "job" ? "border-white border-opacity-40" : ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"job"}
checked={selectedOption === "job"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "job" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my job
</div> </div>
</label> </label>
<label <label

View File

@ -15,6 +15,10 @@ const LLM_DEFAULT = {
requiredConfig: [], requiredConfig: [],
}; };
const LLMS = [LLM_DEFAULT, ...AVAILABLE_LLM_PROVIDERS].filter(
(llm) => !DISABLED_PROVIDERS.includes(llm.value)
);
export default function WorkspaceLLMSelection({ export default function WorkspaceLLMSelection({
settings, settings,
workspace, workspace,
@ -27,9 +31,6 @@ export default function WorkspaceLLMSelection({
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [searchMenuOpen, setSearchMenuOpen] = useState(false); const [searchMenuOpen, setSearchMenuOpen] = useState(false);
const searchInputRef = useRef(null); const searchInputRef = useRef(null);
const LLMS = [LLM_DEFAULT, ...AVAILABLE_LLM_PROVIDERS].filter(
(llm) => !DISABLED_PROVIDERS.includes(llm.value)
);
function updateLLMChoice(selection) { function updateLLMChoice(selection) {
setSearchQuery(""); setSearchQuery("");

View File

@ -0,0 +1,42 @@
import { useState } from "react";
import Workspace from "@/models/workspace";
import showToast from "@/utils/toast";
export default function ResetDatabase({ workspace }) {
const [deleting, setDeleting] = useState(false);
const resetVectorDatabase = async () => {
if (
!window.confirm(
`You are about to reset this workspace's vector database. This will remove all vector embeddings currently embedded.\n\nThe original source files will remain untouched. This action is irreversible.`
)
)
return false;
setDeleting(true);
const success = await Workspace.wipeVectorDb(workspace.slug);
if (!success) {
showToast("Workspace vector database could not be reset!", "error", {
clear: true,
});
setDeleting(false);
return;
}
showToast("Workspace vector database was reset!", "success", {
clear: true,
});
setDeleting(false);
};
return (
<button
disabled={deleting}
onClick={resetVectorDatabase}
type="button"
className="border-none w-fit transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-red-500/25 text-red-200 hover:text-white hover:bg-red-600 disabled:bg-red-600 disabled:text-red-200 disabled:animate-pulse"
>
{deleting ? "Clearing vectors..." : "Reset Workspace Vector Database"}
</button>
);
}

View File

@ -5,6 +5,7 @@ import { useRef, useState } from "react";
import VectorDBIdentifier from "./VectorDBIdentifier"; import VectorDBIdentifier from "./VectorDBIdentifier";
import MaxContextSnippets from "./MaxContextSnippets"; import MaxContextSnippets from "./MaxContextSnippets";
import DocumentSimilarityThreshold from "./DocumentSimilarityThreshold"; import DocumentSimilarityThreshold from "./DocumentSimilarityThreshold";
import ResetDatabase from "./ResetDatabase";
export default function VectorDatabase({ workspace }) { export default function VectorDatabase({ workspace }) {
const [hasChanges, setHasChanges] = useState(false); const [hasChanges, setHasChanges] = useState(false);
@ -43,6 +44,7 @@ export default function VectorDatabase({ workspace }) {
workspace={workspace} workspace={workspace}
setHasChanges={setHasChanges} setHasChanges={setHasChanges}
/> />
<ResetDatabase workspace={workspace} />
{hasChanges && ( {hasChanges && (
<button <button
type="submit" type="submit"

View File

@ -266,6 +266,47 @@ function workspaceEndpoints(app) {
} }
); );
app.delete(
"/workspace/:slug/reset-vector-db",
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
async (request, response) => {
try {
const { slug = "" } = request.params;
const user = await userFromSession(request, response);
const VectorDb = getVectorDbClass();
const workspace = multiUserMode(response)
? await Workspace.getWithUser(user, { slug })
: await Workspace.get({ slug });
if (!workspace) {
response.sendStatus(400).end();
return;
}
await DocumentVectors.deleteForWorkspace(workspace.id);
await Document.delete({ workspaceId: Number(workspace.id) });
await EventLogs.logEvent(
"workspace_vectors_reset",
{
workspaceName: workspace?.name || "Unknown Workspace",
},
response.locals?.user?.id
);
try {
await VectorDb["delete-namespace"]({ namespace: slug });
} catch (e) {
console.error(e.message);
}
response.sendStatus(200).end();
} catch (e) {
console.log(e.message, e);
response.sendStatus(500).end();
}
}
);
app.get( app.get(
"/workspaces", "/workspaces",
[validatedRequest, flexUserRoleValid([ROLES.all])], [validatedRequest, flexUserRoleValid([ROLES.all])],

View File

@ -425,8 +425,8 @@ function validChromaURL(input = "") {
function validAzureURL(input = "") { function validAzureURL(input = "") {
try { try {
new URL(input); new URL(input);
if (!input.includes("openai.azure.com")) if (!input.includes("openai.azure.com") && !input.includes("microsoft.com"))
return "URL must include openai.azure.com"; return "Valid Azure endpoints must contain openai.azure.com OR microsoft.com";
return null; return null;
} catch { } catch {
return "Not a valid URL"; return "Not a valid URL";