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() {
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);
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.
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
);

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.
### 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.
@ -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_
### 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.
@ -34,6 +36,7 @@ While in development mode (`yarn dev`) the script will rebuild on any changes to
### `<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
@ -44,13 +47,14 @@ REQUIRED data attributes:
<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>
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.
@ -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.
**Style Overrides**
- `data-chat-icon` — The chat bubble icon show when chat is closed. Options are `plus`, `chatCircle`, `support`, `search2`, `search`, `magic`.
- `data-button-color` — The chat bubble background color shown when chat is closed. Value must be hex color code.
@ -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-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**
- `data-open-on-load` — Once loaded, open the chat as default. It can still be closed by the user.
- `data-support-email` — Shows a support email that the user can used to draft an email via the "three dot" menu in the top right. Option will not appear if it is not set.
### `<iframe>` tag HTML embed
_work in progress_
### `<iframe>` Customization Options
_work in progress_

View File

@ -19,7 +19,8 @@ const HistoricalMessage = forwardRef(
<div
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
@ -31,7 +32,7 @@ const HistoricalMessage = forwardRef(
>
{role === "assistant" && (
<img
src={AnythingLLMIcon}
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2 mt-2"
id="anything-llm-icon"

View File

@ -13,7 +13,7 @@ const PromptReply = forwardRef(
return (
<div className={`flex items-start w-full h-fit justify-start`}>
<img
src={AnythingLLMIcon}
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2"
/>
@ -33,7 +33,7 @@ const PromptReply = forwardRef(
return (
<div className={`flex items-end w-full h-fit justify-start`}>
<img
src={AnythingLLMIcon}
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon"
className="w-9 h-9 flex-shrink-0 ml-2"
/>
@ -60,7 +60,8 @@ const PromptReply = forwardRef(
<div
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
key={uuid}
@ -68,7 +69,7 @@ const PromptReply = forwardRef(
className={`flex items-start w-full h-fit justify-start`}
>
<img
src={AnythingLLMIcon}
src={embedderSettings.settings.assistantIcon || AnythingLLMIcon}
alt="Anything LLM Icon"
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
sponsorLink: "https://useanything.com", // default sponsor link
position: "bottom-right", // position of chat button/window
assistantName: "AnythingLLM Chat Assistant", // default assistant name
assistantIcon: null, // default assistant icon
// behaviors
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;
},
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) {
const response = await fetch(`${API_BASE}/workspace/${slug}/upload`, {
method: "POST",

View File

@ -8,7 +8,11 @@ const TITLE = "Welcome to AnythingLLM";
const DESCRIPTION = "Help us make AnythingLLM built for your needs. Optional.";
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`, {
method: "POST",
body: JSON.stringify({
@ -38,9 +42,25 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
navigate(paths.onboarding.createWorkspace());
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() {
@ -116,26 +136,24 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
<div className="mt-2 gap-y-3 flex flex-col">
<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 === "business"
? "border-white border-opacity-40"
: ""
selectedOption === "job" ? "border-white border-opacity-40" : ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"business"}
checked={selectedOption === "business"}
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 === "business" ? "bg-white" : ""
selectedOption === "job" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my business
For work
</div>
</label>
<label
@ -159,77 +177,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For 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
For my personal use
</div>
</label>
<label

View File

@ -15,6 +15,10 @@ const LLM_DEFAULT = {
requiredConfig: [],
};
const LLMS = [LLM_DEFAULT, ...AVAILABLE_LLM_PROVIDERS].filter(
(llm) => !DISABLED_PROVIDERS.includes(llm.value)
);
export default function WorkspaceLLMSelection({
settings,
workspace,
@ -27,9 +31,6 @@ export default function WorkspaceLLMSelection({
const [searchQuery, setSearchQuery] = useState("");
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
const searchInputRef = useRef(null);
const LLMS = [LLM_DEFAULT, ...AVAILABLE_LLM_PROVIDERS].filter(
(llm) => !DISABLED_PROVIDERS.includes(llm.value)
);
function updateLLMChoice(selection) {
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 MaxContextSnippets from "./MaxContextSnippets";
import DocumentSimilarityThreshold from "./DocumentSimilarityThreshold";
import ResetDatabase from "./ResetDatabase";
export default function VectorDatabase({ workspace }) {
const [hasChanges, setHasChanges] = useState(false);
@ -43,6 +44,7 @@ export default function VectorDatabase({ workspace }) {
workspace={workspace}
setHasChanges={setHasChanges}
/>
<ResetDatabase workspace={workspace} />
{hasChanges && (
<button
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(
"/workspaces",
[validatedRequest, flexUserRoleValid([ROLES.all])],

View File

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