mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-03-13 05:32:24 +00:00
Compare commits
8 commits
269a467b6a
...
cdaa9454d9
Author | SHA1 | Date | |
---|---|---|---|
|
cdaa9454d9 | ||
|
0e7fee41ca | ||
|
e5f3fb0892 | ||
|
e2148d4803 | ||
|
cc3d619061 | ||
|
4028b5a652 | ||
|
2e0e17ee8a | ||
|
7811d57bb4 |
73 changed files with 4374 additions and 640 deletions
.github/workflows
README.mdcollector/utils/extensions/Confluence
frontend
src
App.jsxindex.css
tailwind.config.jscomponents
Modals/ManageWorkspace/DataConnectors/Connectors/Confluence
PrivateRoute
WorkspaceChat/ChatContainer/ChatHistory
locales
media/logo
models
pages
utils
server
.gitignore
endpoints
index.jsutils
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
|
@ -6,7 +6,7 @@ concurrency:
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: ['3069-tokenizer-collector-improvements'] # put your current branch to create a build. Core team only.
|
||||
branches: ['agent-builder'] # put your current branch to create a build. Core team only.
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'cloud-deployments/*'
|
||||
|
|
|
@ -56,9 +56,10 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace
|
|||
## Cool features of AnythingLLM
|
||||
|
||||
- 🆕 [**Custom AI Agents**](https://docs.anythingllm.com/agent/custom/introduction)
|
||||
- 🆕 [**No-code AI Agent builder**](https://docs.anythingllm.com/agent-flows/overview)
|
||||
- 🖼️ **Multi-modal support (both closed and open-source LLMs!)**
|
||||
- 👤 Multi-user instance support and permissioning _Docker version only_
|
||||
- 🦾 Agents inside your workspace (browse the web, run code, etc)
|
||||
- 🦾 Agents inside your workspace (browse the web, etc)
|
||||
- 💬 [Custom Embeddable Chat widget for your website](./embed/README.md) _Docker version only_
|
||||
- 📖 Multiple document type support (PDF, TXT, DOCX, etc)
|
||||
- Simple chat UI with Drag-n-Drop funcitonality and clear citations.
|
||||
|
|
|
@ -18,15 +18,16 @@ async function loadConfluence(
|
|||
spaceKey = null,
|
||||
username = null,
|
||||
accessToken = null,
|
||||
personalAccessToken = null,
|
||||
cloud = true,
|
||||
},
|
||||
response
|
||||
) {
|
||||
if (!baseUrl || !spaceKey || !username || !accessToken) {
|
||||
if (!accessToken && !personalAccessToken || accessToken && !username || personalAccessToken && username) {
|
||||
return {
|
||||
success: false,
|
||||
reason:
|
||||
"You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.",
|
||||
"You need either a username and access token, or only a personal access token (PAT), to use the Confluence connector.",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -51,6 +52,7 @@ async function loadConfluence(
|
|||
spaceKey,
|
||||
username,
|
||||
accessToken,
|
||||
personalAccessToken,
|
||||
cloud,
|
||||
});
|
||||
|
||||
|
@ -98,7 +100,7 @@ async function loadConfluence(
|
|||
description: doc.metadata.title,
|
||||
docSource: `${origin} Confluence`,
|
||||
chunkSource: generateChunkSource(
|
||||
{ doc, baseUrl: origin, spaceKey, accessToken, username, cloud },
|
||||
{ doc, baseUrl: origin, spaceKey, accessToken, personalAccessToken, username, cloud },
|
||||
response.locals.encryptionWorker
|
||||
),
|
||||
published: new Date().toLocaleString(),
|
||||
|
@ -137,14 +139,14 @@ async function fetchConfluencePage({
|
|||
spaceKey,
|
||||
username,
|
||||
accessToken,
|
||||
personalAccessToken,
|
||||
cloud = true,
|
||||
}) {
|
||||
if (!pageUrl || !baseUrl || !spaceKey || !username || !accessToken) {
|
||||
if (!accessToken && !personalAccessToken || accessToken && !username || personalAccessToken && username) {
|
||||
return {
|
||||
success: false,
|
||||
content: null,
|
||||
reason:
|
||||
"You need either a username and access token, or a personal access token (PAT), to use the Confluence connector.",
|
||||
"You need either a username and access token, or only a personal access token (PAT), to use the Confluence connector.",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -170,6 +172,7 @@ async function fetchConfluencePage({
|
|||
spaceKey,
|
||||
username,
|
||||
accessToken,
|
||||
personalAccessToken,
|
||||
cloud,
|
||||
});
|
||||
|
||||
|
@ -234,9 +237,12 @@ function validBaseUrl(baseUrl) {
|
|||
* @returns {string}
|
||||
*/
|
||||
function generateChunkSource(
|
||||
{ doc, baseUrl, spaceKey, accessToken, username, cloud },
|
||||
{ doc, baseUrl, spaceKey, accessToken, personalAccessToken, username, cloud },
|
||||
encryptionWorker
|
||||
) {
|
||||
if (personalAccessToken) {
|
||||
accessToken = personalAccessToken;
|
||||
}
|
||||
const payload = {
|
||||
baseUrl,
|
||||
spaceKey,
|
||||
|
|
|
@ -67,6 +67,7 @@ const ExperimentalFeatures = lazy(
|
|||
const LiveDocumentSyncManage = lazy(
|
||||
() => import("@/pages/Admin/ExperimentalFeatures/Features/LiveSync/manage")
|
||||
);
|
||||
const AgentBuilder = lazy(() => import("@/pages/Admin/AgentBuilder"));
|
||||
|
||||
const CommunityHubTrending = lazy(
|
||||
() => import("@/pages/GeneralSettings/CommunityHub/Trending")
|
||||
|
@ -143,6 +144,24 @@ export default function App() {
|
|||
path="/settings/agents"
|
||||
element={<AdminRoute Component={AdminAgents} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/agents/builder"
|
||||
element={
|
||||
<AdminRoute
|
||||
Component={AgentBuilder}
|
||||
hideUserMenu={true}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/agents/builder/:flowId"
|
||||
element={
|
||||
<AdminRoute
|
||||
Component={AgentBuilder}
|
||||
hideUserMenu={true}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/event-logs"
|
||||
element={<AdminRoute Component={AdminLogs} />}
|
||||
|
|
|
@ -26,6 +26,7 @@ export default function ConfluenceOptions() {
|
|||
spaceKey: form.get("spaceKey"),
|
||||
username: form.get("username"),
|
||||
accessToken: form.get("accessToken"),
|
||||
personalAccessToken: form.get("personalAccessToken"),
|
||||
cloud: form.get("isCloud") === "true",
|
||||
});
|
||||
|
||||
|
@ -133,7 +134,7 @@ export default function ConfluenceOptions() {
|
|||
name="username"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="jdoe@example.com"
|
||||
required={true}
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
@ -157,7 +158,7 @@ export default function ConfluenceOptions() {
|
|||
clickable={true}
|
||||
>
|
||||
<p className="text-sm">
|
||||
You need to provide an access token for authentication.
|
||||
You need to provide a username and access token or a personal access token for authentication.
|
||||
You can generate an access token{" "}
|
||||
<a
|
||||
href="https://id.atlassian.com/manage-profile/security/api-tokens"
|
||||
|
@ -181,7 +182,45 @@ export default function ConfluenceOptions() {
|
|||
name="accessToken"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="abcd1234"
|
||||
required={true}
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pr-10">
|
||||
<div className="flex flex-col gap-y-1 mb-4">
|
||||
<label className="text-white text-sm font-bold flex gap-x-2 items-center">
|
||||
<p className="font-bold text-white">
|
||||
Confluence Personal Access Token
|
||||
</p>
|
||||
<Warning
|
||||
size={14}
|
||||
className="ml-1 text-orange-500 cursor-pointer"
|
||||
data-tooltip-id="personal-access-token-tooltip"
|
||||
data-tooltip-place="right"
|
||||
/>
|
||||
<Tooltip
|
||||
delayHide={300}
|
||||
id="personal-access-token-tooltip"
|
||||
className="max-w-xs z-99"
|
||||
clickable={true}
|
||||
>
|
||||
<p className="text-sm">
|
||||
You need to either provide a personal access token or a username and password for authentication.
|
||||
You can create a personal access token in your confluence user settings
|
||||
</p>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<p className="text-xs font-normal text-theme-text-secondary">
|
||||
Personal access token for authentication.
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="personalAccessToken"
|
||||
className="bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="abcd1234"
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
|
|
@ -83,7 +83,7 @@ function useIsAuthenticated() {
|
|||
|
||||
// Allows only admin to access the route and if in single user mode,
|
||||
// allows all users to access the route
|
||||
export function AdminRoute({ Component }) {
|
||||
export function AdminRoute({ Component, hideUserMenu = false }) {
|
||||
const { isAuthd, shouldRedirectToOnboarding, multiUserMode } =
|
||||
useIsAuthenticated();
|
||||
if (isAuthd === null) return <FullScreenLoader />;
|
||||
|
@ -94,9 +94,13 @@ export function AdminRoute({ Component }) {
|
|||
|
||||
const user = userFromStorage();
|
||||
return isAuthd && (user?.role === "admin" || !multiUserMode) ? (
|
||||
<UserMenu>
|
||||
hideUserMenu ? (
|
||||
<Component />
|
||||
</UserMenu>
|
||||
) : (
|
||||
<UserMenu>
|
||||
<Component />
|
||||
</UserMenu>
|
||||
)
|
||||
) : (
|
||||
<Navigate to={paths.home()} />
|
||||
);
|
||||
|
|
|
@ -18,6 +18,10 @@ import {
|
|||
} from "../ThoughtContainer";
|
||||
|
||||
const DOMPurify = createDOMPurify(window);
|
||||
DOMPurify.setConfig({
|
||||
ADD_ATTR: ["target", "rel"],
|
||||
});
|
||||
|
||||
const HistoricalMessage = ({
|
||||
uuid = v4(),
|
||||
message,
|
||||
|
|
|
@ -71,7 +71,9 @@ export default function StatusResponse({
|
|||
<div
|
||||
key={`cot-list-${currentThought.uuid}`}
|
||||
className={`mt-2 bg-theme-bg-chat-input backdrop-blur-sm rounded-lg overflow-hidden transition-all duration-300 border border-theme-sidebar-border ${
|
||||
isExpanded ? "max-h-[300px] opacity-100" : "max-h-0 opacity-0"
|
||||
isExpanded
|
||||
? "max-h-[300px] overflow-y-auto opacity-100"
|
||||
: "max-h-0 opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="p-2">
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
--theme-button-primary: #46c8ff;
|
||||
--theme-button-primary-hover: #434343;
|
||||
|
||||
--theme-button-cta: #7cd4fd;
|
||||
|
||||
--theme-file-row-even: #0e0f0f;
|
||||
--theme-file-row-odd: #1b1b1e;
|
||||
--theme-file-row-selected-even: rgba(14, 165, 233, 0.2);
|
||||
|
@ -92,6 +94,8 @@
|
|||
--theme-button-primary: #0ba5ec;
|
||||
--theme-button-primary-hover: #dedede;
|
||||
|
||||
--theme-button-cta: #7cd4fd;
|
||||
|
||||
--theme-file-row-even: #f5f5f5;
|
||||
--theme-file-row-odd: #e9e9e9;
|
||||
--theme-file-row-selected-even: #0ba5ec;
|
||||
|
@ -664,6 +668,11 @@ dialog::backdrop {
|
|||
padding: 14px 15px;
|
||||
}
|
||||
|
||||
.markdown > * a {
|
||||
color: var(--theme-button-cta);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.markdown table th,
|
||||
.markdown table td {
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Name der Arbeitsbereiche",
|
||||
error: "Fehler",
|
||||
|
@ -9,8 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Änderungen speichern",
|
||||
previous: "Vorherige Seite",
|
||||
next: "Nächste Seite",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
settings: {
|
||||
title: "Instanzeinstellungen",
|
||||
system: "Allgemeine Einstellungen",
|
||||
|
@ -39,7 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Support kontaktieren",
|
||||
"browser-extension": "Browser-Erweiterung",
|
||||
},
|
||||
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Willkommen bei",
|
||||
|
@ -63,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Zurück zur Anmeldung",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Willkommen bei AnythingLLM, AnythingLLM ist ein Open-Source-KI-Tool von Mintplex Labs, das alles in einen trainierten Chatbot verwandelt, den Sie abfragen und mit dem Sie chatten können. AnythingLLM ist eine BYOK-Software (Bring-Your-Own-Keys), daher gibt es keine Abonnements, Gebühren oder Kosten für diese Software außerhalb der Dienste, die Sie damit nutzen möchten.",
|
||||
|
@ -85,12 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Stern auf GitHub",
|
||||
contact: "Kontaktieren Sie Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Neuer Arbeitsbereich",
|
||||
placeholder: "Mein Arbeitsbereich",
|
||||
},
|
||||
|
||||
"workspaces—settings": {
|
||||
general: "Allgemeine Einstellungen",
|
||||
chat: "Chat-Einstellungen",
|
||||
|
@ -98,7 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Mitglieder",
|
||||
agent: "Agentenkonfiguration",
|
||||
},
|
||||
|
||||
general: {
|
||||
vector: {
|
||||
title: "Vektoranzahl",
|
||||
|
@ -134,7 +180,6 @@ const TRANSLATIONS = {
|
|||
"Arbeitsbereich zu löschen. Dies entfernt alle Vektoreinbettungen in Ihrer Vektordatenbank.\n\nDie ursprünglichen Quelldateien bleiben unberührt. Diese Aktion ist irreversibel.",
|
||||
},
|
||||
},
|
||||
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Arbeitsbereich-LLM-Anbieter",
|
||||
|
@ -192,7 +237,6 @@ const TRANSLATIONS = {
|
|||
hint: "Die meisten LLMs haben verschiedene akzeptable Bereiche gültiger Werte. Konsultieren Sie Ihren LLM-Anbieter für diese Informationen.",
|
||||
},
|
||||
},
|
||||
|
||||
"vector-workspace": {
|
||||
identifier: "Vektordatenbank-Identifikator",
|
||||
snippets: {
|
||||
|
@ -220,7 +264,6 @@ const TRANSLATIONS = {
|
|||
success: "Die Arbeitsbereich-Vektordatenbank wurde zurückgesetzt!",
|
||||
},
|
||||
},
|
||||
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"Die Leistung von LLMs, die Werkzeugaufrufe nicht explizit unterstützen, hängt stark von den Fähigkeiten und der Genauigkeit des Modells ab. Einige Fähigkeiten können eingeschränkt oder nicht funktionsfähig sein.",
|
||||
|
@ -240,7 +283,6 @@ const TRANSLATIONS = {
|
|||
"Das spezifische LLM-Modell, das für den @agent-Agenten dieses Arbeitsbereichs verwendet wird.",
|
||||
wait: "-- warte auf Modelle --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Standard-Agentenfähigkeiten",
|
||||
description:
|
||||
|
@ -279,7 +321,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
recorded: {
|
||||
title: "Arbeitsbereich-Chats",
|
||||
description:
|
||||
|
@ -294,7 +335,6 @@ const TRANSLATIONS = {
|
|||
at: "Gesendet am",
|
||||
},
|
||||
},
|
||||
|
||||
appearance: {
|
||||
title: "Erscheinungsbild",
|
||||
description: "Passen Sie die Erscheinungseinstellungen Ihrer Plattform an.",
|
||||
|
@ -327,7 +367,6 @@ const TRANSLATIONS = {
|
|||
link: "Link",
|
||||
},
|
||||
},
|
||||
|
||||
api: {
|
||||
title: "API-Schlüssel",
|
||||
description:
|
||||
|
@ -340,14 +379,12 @@ const TRANSLATIONS = {
|
|||
created: "Erstellt",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM-Präferenz",
|
||||
description:
|
||||
"Dies sind die Anmeldeinformationen und Einstellungen für Ihren bevorzugten LLM-Chat- und Einbettungsanbieter. Es ist wichtig, dass diese Schlüssel aktuell und korrekt sind, sonst wird AnythingLLM nicht richtig funktionieren.",
|
||||
provider: "LLM-Anbieter",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Transkriptionsmodell-Präferenz",
|
||||
description:
|
||||
|
@ -360,7 +397,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"Das eingebaute Modell wird bei der ersten Verwendung automatisch heruntergeladen.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Einbettungspräferenz",
|
||||
"desc-start":
|
||||
|
@ -373,7 +409,6 @@ const TRANSLATIONS = {
|
|||
"Bei Verwendung der nativen Einbettungs-Engine von AnythingLLM ist keine Einrichtung erforderlich.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Textsplitting & Chunking-Präferenzen",
|
||||
"desc-start":
|
||||
|
@ -389,14 +424,12 @@ const TRANSLATIONS = {
|
|||
"Dies ist die maximale Länge der Zeichen, die in einem einzelnen Vektor vorhanden sein können.",
|
||||
recommend: "Die maximale Länge des Einbettungsmodells beträgt",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Textchunk-Überlappung",
|
||||
description:
|
||||
"Dies ist die maximale Überlappung von Zeichen, die während des Chunkings zwischen zwei benachbarten Textchunks auftritt.",
|
||||
},
|
||||
},
|
||||
|
||||
vector: {
|
||||
title: "Vektordatenbank",
|
||||
description:
|
||||
|
@ -406,7 +439,6 @@ const TRANSLATIONS = {
|
|||
description: "Für LanceDB ist keine Konfiguration erforderlich.",
|
||||
},
|
||||
},
|
||||
|
||||
embeddable: {
|
||||
title: "Einbettbare Chat-Widgets",
|
||||
description:
|
||||
|
@ -418,7 +450,6 @@ const TRANSLATIONS = {
|
|||
Active: "Aktive Domains",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Eingebettete Chats",
|
||||
export: "Exportieren",
|
||||
|
@ -432,7 +463,6 @@ const TRANSLATIONS = {
|
|||
at: "Gesendet am",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Mehrbenutzer-Modus",
|
||||
description:
|
||||
|
@ -457,7 +487,6 @@ const TRANSLATIONS = {
|
|||
password: "Instanz-Passwort",
|
||||
},
|
||||
},
|
||||
|
||||
event: {
|
||||
title: "Ereignisprotokolle",
|
||||
description:
|
||||
|
@ -469,7 +498,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Aufgetreten am",
|
||||
},
|
||||
},
|
||||
|
||||
privacy: {
|
||||
title: "Datenschutz & Datenverarbeitung",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,62 @@
|
|||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
home: {
|
||||
title: "Welcome to",
|
||||
getStarted: "Get Started",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM Preference",
|
||||
description:
|
||||
"AnythingLLM can work with many LLM providers. This will be the service which handles chatting.",
|
||||
},
|
||||
userSetup: {
|
||||
title: "User Setup",
|
||||
description: "Configure your user settings.",
|
||||
howManyUsers: "How many users will be using this instance?",
|
||||
justMe: "Just me",
|
||||
myTeam: "My team",
|
||||
instancePassword: "Instance Password",
|
||||
setPassword: "Would you like to set up a password?",
|
||||
passwordReq: "Passwords must be at least 8 characters.",
|
||||
passwordWarn:
|
||||
"It's important to save this password because there is no recovery method.",
|
||||
|
||||
adminUsername: "Admin account username",
|
||||
adminUsernameReq:
|
||||
"Username must be at least 6 characters long and only contain lowercase letters, numbers, underscores, and hyphens with no spaces.",
|
||||
adminPassword: "Admin account password",
|
||||
adminPasswordReq: "Passwords must be at least 8 characters.",
|
||||
teamHint:
|
||||
"By default, you will be the only admin. Once onboarding is completed you can create and invite others to be users or admins. Do not lose your password as only admins can reset passwords.",
|
||||
},
|
||||
data: {
|
||||
title: "Data Handling & Privacy",
|
||||
description:
|
||||
"We are committed to transparency and control when it comes to your personal data.",
|
||||
settingsHint:
|
||||
"These settings can be reconfigured at any time in the settings.",
|
||||
},
|
||||
survey: {
|
||||
title: "Welcome to AnythingLLM",
|
||||
description: "Help us make AnythingLLM built for your needs. Optional.",
|
||||
|
||||
email: "What's your email?",
|
||||
useCase: "What will you use AnythingLLM for?",
|
||||
useCaseWork: "For work",
|
||||
useCasePersonal: "For personal use",
|
||||
useCaseOther: "Other",
|
||||
comment: "How did you hear about AnythingLLM?",
|
||||
commentPlaceholder:
|
||||
"Reddit, Twitter, GitHub, YouTube, etc. - Let us know how you found us!",
|
||||
skip: "Skip Survey",
|
||||
thankYou: "Thank you for your feedback!",
|
||||
},
|
||||
workspace: {
|
||||
title: "Create your first workspace",
|
||||
description:
|
||||
"Create your first workspace and get started with AnythingLLM.",
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Workspaces Name",
|
||||
error: "error",
|
||||
|
@ -9,6 +67,9 @@ const TRANSLATIONS = {
|
|||
save: "Save changes",
|
||||
previous: "Previous Page",
|
||||
next: "Next Page",
|
||||
optional: "Optional",
|
||||
yes: "Yes",
|
||||
no: "No",
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Nombre de espacios de trabajo",
|
||||
error: "error",
|
||||
|
@ -9,8 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Guardar cambios",
|
||||
previous: "Página anterior",
|
||||
next: "Página siguiente",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
settings: {
|
||||
title: "Configuración de instancia",
|
||||
system: "Preferencias del sistema",
|
||||
|
@ -39,7 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Contactar Soporte",
|
||||
"browser-extension": "Extensión del navegador",
|
||||
},
|
||||
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Bienvenido a",
|
||||
|
@ -63,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Volver al inicio de sesión",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Bienvenido a AnythingLLM, una herramienta de inteligencia artificial de código abierto creada por Mintplex Labs que convierte cualquier cosa en un chatbot entrenado con el que puedes consultar y conversar. AnythingLLM es un software BYOK (bring-your-own-keys), por lo que no hay suscripciones, tarifas ni cargos por este software, salvo por los servicios que deseas utilizar.",
|
||||
|
@ -85,12 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Estrella en GitHub",
|
||||
contact: "Contactar a Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Nuevo Espacio de Trabajo",
|
||||
placeholder: "Mi Espacio de Trabajo",
|
||||
},
|
||||
|
||||
"workspaces—settings": {
|
||||
general: "Configuración general",
|
||||
chat: "Configuración de chat",
|
||||
|
@ -98,7 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Miembros",
|
||||
agent: "Configuración del agente",
|
||||
},
|
||||
|
||||
general: {
|
||||
vector: {
|
||||
title: "Conteo de vectores",
|
||||
|
@ -135,7 +181,6 @@ const TRANSLATIONS = {
|
|||
"espacio de trabajo. Esto eliminará todas las incrustaciones de vectores en tu base de datos de vectores.\n\nLos archivos de origen originales permanecerán intactos. Esta acción es irreversible.",
|
||||
},
|
||||
},
|
||||
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Proveedor LLM del espacio de trabajo",
|
||||
|
@ -194,7 +239,6 @@ const TRANSLATIONS = {
|
|||
hint: "La mayoría de los LLM tienen varios rangos aceptables de valores válidos. Consulta a tu proveedor de LLM para obtener esa información.",
|
||||
},
|
||||
},
|
||||
|
||||
"vector-workspace": {
|
||||
identifier: "Identificador de la base de datos de vectores",
|
||||
snippets: {
|
||||
|
@ -223,7 +267,6 @@ const TRANSLATIONS = {
|
|||
"¡La base de datos de vectores del espacio de trabajo fue restablecida!",
|
||||
},
|
||||
},
|
||||
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"El rendimiento de los LLM que no admiten explícitamente la llamada de herramientas depende en gran medida de las capacidades y la precisión del modelo. Algunas habilidades pueden estar limitadas o no funcionar.",
|
||||
|
@ -243,7 +286,6 @@ const TRANSLATIONS = {
|
|||
"El modelo LLM específico que se utilizará para el agente @agent de este espacio de trabajo.",
|
||||
wait: "-- esperando modelos --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Habilidades predeterminadas del agente",
|
||||
description:
|
||||
|
@ -282,7 +324,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
recorded: {
|
||||
title: "Chats del espacio de trabajo",
|
||||
description:
|
||||
|
@ -297,7 +338,6 @@ const TRANSLATIONS = {
|
|||
at: "Enviado a",
|
||||
},
|
||||
},
|
||||
|
||||
appearance: {
|
||||
title: "Apariencia",
|
||||
description: "Personaliza la configuración de apariencia de tu plataforma.",
|
||||
|
@ -330,7 +370,6 @@ const TRANSLATIONS = {
|
|||
link: "Enlace",
|
||||
},
|
||||
},
|
||||
|
||||
api: {
|
||||
title: "Claves API",
|
||||
description:
|
||||
|
@ -343,14 +382,12 @@ const TRANSLATIONS = {
|
|||
created: "Creado",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "Preferencia de LLM",
|
||||
description:
|
||||
"Estas son las credenciales y configuraciones para tu proveedor preferido de chat y incrustación de LLM. Es importante que estas claves estén actualizadas y correctas, de lo contrario AnythingLLM no funcionará correctamente.",
|
||||
provider: "Proveedor de LLM",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Preferencia de modelo de transcripción",
|
||||
description:
|
||||
|
@ -363,7 +400,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"El modelo incorporado se descargará automáticamente en el primer uso.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Preferencia de incrustación",
|
||||
"desc-start":
|
||||
|
@ -376,7 +412,6 @@ const TRANSLATIONS = {
|
|||
"No se requiere configuración cuando se utiliza el motor de incrustación nativo de AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Preferencias de división y fragmentación de texto",
|
||||
"desc-start":
|
||||
|
@ -392,14 +427,12 @@ const TRANSLATIONS = {
|
|||
"Esta es la longitud máxima de caracteres que puede estar presente en un solo vector.",
|
||||
recommend: "La longitud máxima del modelo de incrustación es",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Superposición de fragmentos de texto",
|
||||
description:
|
||||
"Esta es la superposición máxima de caracteres que ocurre durante la fragmentación entre dos fragmentos de texto adyacentes.",
|
||||
},
|
||||
},
|
||||
|
||||
vector: {
|
||||
title: "Base de datos de vectores",
|
||||
description:
|
||||
|
@ -409,7 +442,6 @@ const TRANSLATIONS = {
|
|||
description: "No se necesita configuración para LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
embeddable: {
|
||||
title: "Widgets de chat incrustables",
|
||||
description:
|
||||
|
@ -421,7 +453,6 @@ const TRANSLATIONS = {
|
|||
Active: "Dominios activos",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Incrustar chats",
|
||||
export: "Exportar",
|
||||
|
@ -435,7 +466,6 @@ const TRANSLATIONS = {
|
|||
at: "Enviado a",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Modo multiusuario",
|
||||
description:
|
||||
|
@ -460,7 +490,6 @@ const TRANSLATIONS = {
|
|||
password: "Contraseña de la instancia",
|
||||
},
|
||||
},
|
||||
|
||||
event: {
|
||||
title: "Registros de eventos",
|
||||
description:
|
||||
|
@ -472,7 +501,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Ocurrido a",
|
||||
},
|
||||
},
|
||||
|
||||
privacy: {
|
||||
title: "Privacidad y manejo de datos",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "نام فضای کار",
|
||||
error: "خطا",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "ذخیره تغییرات",
|
||||
previous: "صفحه قبلی",
|
||||
next: "صفحه بعدی",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "تنظیمات سامانه",
|
||||
system: "تنظیمات عمومی",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "تماس با پشتیبانی",
|
||||
"browser-extension": "افزونه مرورگر",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "خوش آمدید به",
|
||||
|
@ -64,7 +112,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "بازگشت به صفحه ورود",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"به AnythingLLM خوش آمدید. AnythingLLM یک ابزار هوش مصنوعی متنباز توسط Mintplex Labs است که هر چیزی را به یک ربات گفتگوی آموزشدیده تبدیل میکند که میتوانید با آن گفتگو و پرسوجو کنید. AnythingLLM یک نرمافزار BYOK (آوردن کلیدهای خودتان) است، بنابراین هیچ اشتراک، هزینه یا مبلغی برای این نرمافزار خارج از سرویسهایی که میخواهید با آن استفاده کنید، وجود ندارد.",
|
||||
|
@ -86,13 +133,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "ستاره در گیتهاب",
|
||||
contact: "تماس با Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "فضای کاری جدید",
|
||||
placeholder: "فضای کاری من",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "تنظیمات عمومی",
|
||||
chat: "تنظیمات گفتگو",
|
||||
|
@ -100,8 +144,6 @@ const TRANSLATIONS = {
|
|||
members: "اعضا",
|
||||
agent: "پیکربندی عامل",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "تعداد بردارها",
|
||||
|
@ -136,8 +178,6 @@ const TRANSLATIONS = {
|
|||
"فضای کاری هستید. این کار تمام جاسازیهای برداری را از پایگاه داده برداری شما حذف خواهد کرد.\n\nفایلهای اصلی منبع دست نخورده باقی خواهند ماند. این عمل برگشتناپذیر است.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "ارائهدهنده LLM فضای کاری",
|
||||
|
@ -195,8 +235,6 @@ const TRANSLATIONS = {
|
|||
hint: "اکثر LLMها محدودههای مختلفی از مقادیر معتبر را دارند. برای این اطلاعات به ارائهدهنده LLM خود مراجعه کنید.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "شناسه پایگاه داده برداری",
|
||||
snippets: {
|
||||
|
@ -223,8 +261,6 @@ const TRANSLATIONS = {
|
|||
success: "پایگاه داده برداری فضای کاری بازنشانی شد!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"عملکرد LLMهایی که به طور صریح از فراخوانی ابزار پشتیبانی نمیکنند، به شدت به قابلیتها و دقت مدل وابسته است. برخی تواناییها ممکن است محدود یا غیرفعال باشند.",
|
||||
|
@ -282,8 +318,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "گفتگوهای فضای کاری",
|
||||
description:
|
||||
|
@ -298,8 +332,6 @@ const TRANSLATIONS = {
|
|||
at: "زمان ارسال",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "ظاهر",
|
||||
description: "تنظیمات ظاهری پلتفرم خود را شخصیسازی کنید.",
|
||||
|
@ -331,8 +363,6 @@ const TRANSLATIONS = {
|
|||
link: "لینک",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "کلیدهای API",
|
||||
description:
|
||||
|
@ -345,14 +375,12 @@ const TRANSLATIONS = {
|
|||
created: "تاریخ ایجاد",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "ترجیحات مدل زبانی",
|
||||
description:
|
||||
"اینها اعتبارنامهها و تنظیمات ارائهدهنده مدل زبانی و جاسازی انتخابی شما هستند. مهم است که این کلیدها بهروز و صحیح باشند در غیر این صورت AnythingLLM به درستی کار نخواهد کرد.",
|
||||
provider: "ارائهدهنده مدل زبانی",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "ترجیحات مدل رونویسی",
|
||||
description:
|
||||
|
@ -364,7 +392,6 @@ const TRANSLATIONS = {
|
|||
"ما حداقل ۲ گیگابایت RAM و آپلود فایلهای کمتر از ۱۰ مگابایت را توصیه میکنیم.",
|
||||
"warn-end": "مدل داخلی در اولین استفاده به صورت خودکار دانلود خواهد شد.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "ترجیحات جاسازی",
|
||||
"desc-start":
|
||||
|
@ -377,7 +404,6 @@ const TRANSLATIONS = {
|
|||
"هنگام استفاده از موتور جاسازی داخلی AnythingLLM نیازی به تنظیمات نیست.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "تقسیم متن و تکهبندی",
|
||||
"desc-start":
|
||||
|
@ -399,8 +425,6 @@ const TRANSLATIONS = {
|
|||
"این حداکثر همپوشانی کاراکترها است که در هنگام تکهبندی بین دو بخش متن مجاور رخ میدهد.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "پایگاه داده برداری",
|
||||
description:
|
||||
|
@ -410,8 +434,6 @@ const TRANSLATIONS = {
|
|||
description: "برای LanceDB نیازی به پیکربندی نیست.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "جاسازی گفتگو",
|
||||
description:
|
||||
|
@ -423,7 +445,6 @@ const TRANSLATIONS = {
|
|||
Active: "دامنههای فعال",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "گفتگوهای جاسازی شده",
|
||||
export: "خروجیگیری",
|
||||
|
@ -437,7 +458,6 @@ const TRANSLATIONS = {
|
|||
at: "زمان ارسال",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "حالت چند کاربره",
|
||||
description:
|
||||
|
@ -462,8 +482,6 @@ const TRANSLATIONS = {
|
|||
password: "رمز عبور نمونه",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "گزارش رویدادها",
|
||||
description:
|
||||
|
@ -475,8 +493,6 @@ const TRANSLATIONS = {
|
|||
occurred: "زمان وقوع",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "حریم خصوصی و مدیریت دادهها",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Nom des espaces de travail",
|
||||
error: "erreur",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Enregistrer les modifications",
|
||||
previous: "Page précédente",
|
||||
next: "Page suivante",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Paramètres de l'instance",
|
||||
system: "Préférences système",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Contacter le Support",
|
||||
"browser-extension": "Extension de navigateur",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Bienvenue à",
|
||||
|
@ -65,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Retour à la connexion",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Bienvenue sur AnythingLLM, un outil d'intelligence artificielle open-source créé par Mintplex Labs qui transforme n'importe quoi en un chatbot entraîné avec lequel vous pouvez interroger et discuter. AnythingLLM est un logiciel BYOK (apportez vos propres clés), il n'y a donc pas d'abonnement, de frais ou de charges pour ce logiciel en dehors des services que vous souhaitez utiliser.",
|
||||
|
@ -87,13 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Étoile sur GitHub",
|
||||
contact: "Contacter Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Nouveau Espace de Travail",
|
||||
placeholder: "Mon Espace de Travail",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Paramètres généraux",
|
||||
chat: "Paramètres de chat",
|
||||
|
@ -101,8 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Membres",
|
||||
agent: "Configuration de l'agent",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Nombre de vecteurs",
|
||||
|
@ -140,8 +182,6 @@ const TRANSLATIONS = {
|
|||
"espace de travail. Cela supprimera toutes les intégrations vectorielles dans votre base de données vectorielle.\n\nLes fichiers source originaux resteront intacts. Cette action est irréversible.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Fournisseur LLM de l'espace de travail",
|
||||
|
@ -200,8 +240,6 @@ const TRANSLATIONS = {
|
|||
hint: "La plupart des LLM ont diverses plages acceptables de valeurs valides. Consultez votre fournisseur LLM pour cette information.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Identifiant de la base de données vectorielle",
|
||||
snippets: {
|
||||
|
@ -230,8 +268,6 @@ const TRANSLATIONS = {
|
|||
"La base de données vectorielle de l'espace de travail a été réinitialisée !",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"La performance des LLM qui ne supportent pas explicitement l'appel d'outils dépend fortement des capacités et de la précision du modèle. Certaines capacités peuvent être limitées ou non fonctionnelles.",
|
||||
|
@ -251,7 +287,6 @@ const TRANSLATIONS = {
|
|||
"Le modèle LLM spécifique qui sera utilisé pour l'agent @agent de cet espace de travail.",
|
||||
wait: "-- en attente des modèles --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Compétences par défaut de l'agent",
|
||||
description:
|
||||
|
@ -290,8 +325,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Chats de l'espace de travail",
|
||||
description:
|
||||
|
@ -306,8 +339,6 @@ const TRANSLATIONS = {
|
|||
at: "Envoyé à",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Apparence",
|
||||
description:
|
||||
|
@ -341,8 +372,6 @@ const TRANSLATIONS = {
|
|||
link: "Lien",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "Clés API",
|
||||
description:
|
||||
|
@ -355,14 +384,12 @@ const TRANSLATIONS = {
|
|||
created: "Créé",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "Préférence LLM",
|
||||
description:
|
||||
"Voici les identifiants et les paramètres de votre fournisseur LLM de chat et d'intégration préféré. Il est important que ces clés soient actuelles et correctes, sinon AnythingLLM ne fonctionnera pas correctement.",
|
||||
provider: "Fournisseur LLM",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Préférence du modèle de transcription",
|
||||
description:
|
||||
|
@ -375,7 +402,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"Le modèle intégré se téléchargera automatiquement lors de la première utilisation.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Préférence d'intégration",
|
||||
"desc-start":
|
||||
|
@ -388,7 +414,6 @@ const TRANSLATIONS = {
|
|||
"Aucune configuration n'est nécessaire lors de l'utilisation du moteur d'intégration natif de AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Préférences de division et de découpage du texte",
|
||||
"desc-start":
|
||||
|
@ -404,15 +429,12 @@ const TRANSLATIONS = {
|
|||
"C'est la longueur maximale de caractères pouvant être présents dans un seul vecteur.",
|
||||
recommend: "Longueur maximale du modèle d'intégration est",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Chevauchement des segments de texte",
|
||||
description:
|
||||
"C'est le chevauchement maximal de caractères qui se produit pendant le découpage entre deux segments de texte adjacents.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Base de données vectorielle",
|
||||
description:
|
||||
|
@ -422,8 +444,6 @@ const TRANSLATIONS = {
|
|||
description: "Aucune configuration n'est nécessaire pour LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Widgets de chat intégrables",
|
||||
description:
|
||||
|
@ -435,7 +455,6 @@ const TRANSLATIONS = {
|
|||
Active: "Domaines actifs",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Chats intégrés",
|
||||
export: "Exporter",
|
||||
|
@ -449,7 +468,6 @@ const TRANSLATIONS = {
|
|||
at: "Envoyé à",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Mode multi-utilisateurs",
|
||||
description:
|
||||
|
@ -474,8 +492,6 @@ const TRANSLATIONS = {
|
|||
password: "Mot de passe de l'instance",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Journaux d'événements",
|
||||
description:
|
||||
|
@ -487,8 +503,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Survenu à",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Confidentialité et gestion des données",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "שם סביבת העבודה",
|
||||
error: "שגיאה",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "שמור שינויים",
|
||||
previous: "עמוד קודם",
|
||||
next: "עמוד הבא",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "הגדרות מופע",
|
||||
system: "הגדרות כלליות",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "צור קשר עם התמיכה",
|
||||
"browser-extension": "תוסף דפדפן",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "ברוכים הבאים ל-",
|
||||
|
@ -64,7 +112,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "חזרה להתחברות",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"ברוכים הבאים ל-AnythingLLM, AnythingLLM היא כלי AI קוד פתוח מאת Mintplex Labs שהופך כל דבר לצ'אטבוט מאומן שאפשר לשאול אותו ולקיים איתו שיחה. AnythingLLM הוא תוכנה מסוג BYOK (הביא את המפתחות שלך) כך שאין מנוי, עמלה או חיובים עבור התוכנה הזו מלבד השירותים שאתה רוצה להשתמש בהם.",
|
||||
|
@ -85,13 +132,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "שים כוכב ב-GitHub",
|
||||
contact: "צור קשר עם Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "סביבת עבודה חדשה",
|
||||
placeholder: "סביבת העבודה שלי",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "הגדרות כלליות",
|
||||
chat: "הגדרות צ'אט",
|
||||
|
@ -99,8 +143,6 @@ const TRANSLATIONS = {
|
|||
members: "חברים",
|
||||
agent: "קונפיגורציה של סוכן",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "ספירת וקטורים",
|
||||
|
@ -135,8 +177,6 @@ const TRANSLATIONS = {
|
|||
"סביבת העבודה שלך. זה ימחק את כל הטבעות הווקטוריות בבסיס הנתונים הווקטורי שלך.\n\nקבצי המקור המקוריים יישארו ללא שינוי. פעולה זו היא בלתי הפיכה.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "ספק LLM של סביבת עבודה",
|
||||
|
@ -193,8 +233,6 @@ const TRANSLATIONS = {
|
|||
hint: "לרוב ה-LLM יש טווחים שונים של ערכים תקפים. התייעץ עם ספק ה-LLM שלך לקבלת מידע.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "מזהה בסיס נתונים וקטור",
|
||||
snippets: {
|
||||
|
@ -221,8 +259,6 @@ const TRANSLATIONS = {
|
|||
success: "בסיס הנתונים הווקטורי של סביבת העבודה איפס!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"ביצועי LLM שאינם תומכים באופן מפורש בקריאות כלים תלויים מאוד ביכולות ובדיוק של הדגם. חלק מהיכולות עשויות להיות מוגבלות או לא פונקציונליות.",
|
||||
|
@ -242,7 +278,6 @@ const TRANSLATIONS = {
|
|||
"דגם LLM ספציפי שייעשה בו שימוש עבור סוכן @agent של סביבת העבודה הזו.",
|
||||
wait: "-- מחכה לדגמים --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "כישורי סוכן ברירת מחדל",
|
||||
description:
|
||||
|
@ -279,8 +314,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "שיחות סביבת עבודה",
|
||||
description:
|
||||
|
@ -295,8 +328,6 @@ const TRANSLATIONS = {
|
|||
at: "נשלח ב-",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "מראה",
|
||||
description: "התאם אישית את הגדרות המראה של הפלטפורמה שלך.",
|
||||
|
@ -327,8 +358,6 @@ const TRANSLATIONS = {
|
|||
link: "קישור",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "מפתחות API",
|
||||
description:
|
||||
|
@ -341,14 +370,12 @@ const TRANSLATIONS = {
|
|||
created: "נוצר",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "העדפת LLM",
|
||||
description:
|
||||
"אלה אישורי ההרשאה וההגדרות עבור ספק צ'אט והטבעה LLM המועדף עליך. חשוב שאישורי ההרשאה יהיו עדכניים ונכונים אחרת AnythingLLM לא תוכל לפעול כראוי.",
|
||||
provider: "ספק LLM",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "העדפת דגם תמלול",
|
||||
description:
|
||||
|
@ -360,7 +387,6 @@ const TRANSLATIONS = {
|
|||
"אנו ממליצים על לפחות 2GB של זיכרון RAM והעלאת קבצים <10Mb.",
|
||||
"warn-end": "הדגם המובנה יתורגם אוטומטית בפעם הראשונה שבה תשתמש בו.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "העדפת הטבעה",
|
||||
"desc-start":
|
||||
|
@ -373,7 +399,6 @@ const TRANSLATIONS = {
|
|||
"אין צורך בהגדרה בעת שימוש במנוע ההטבעה המקורי של AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "הגדרות חלוקת טקסט וחלוקה",
|
||||
"desc-start":
|
||||
|
@ -388,15 +413,12 @@ const TRANSLATIONS = {
|
|||
description: "זהו אורך הדמויות המרבי שיכול להיות נוכח בקטור יחיד.",
|
||||
recommend: "אורך מרבי של דגם ההטבעה הוא",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "חפיפה של קטע טקסט",
|
||||
description:
|
||||
"זו החפיפה המרבית של הדמויות המתרחשת במהלך החלוקה בין שני קטעי טקסט סמוכים.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "בסיס נתונים וקטור",
|
||||
description:
|
||||
|
@ -406,8 +428,6 @@ const TRANSLATIONS = {
|
|||
description: "אין צורך בקונפיגורציה עבור LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "כלי צ'אט ניתנים להטמעה",
|
||||
description:
|
||||
|
@ -419,7 +439,6 @@ const TRANSLATIONS = {
|
|||
Active: "תחומים פעילים",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "הטמעת שיחות",
|
||||
export: "ייצוא",
|
||||
|
@ -432,7 +451,6 @@ const TRANSLATIONS = {
|
|||
at: "נשלח ב-",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "מצב משתמשים מרובים",
|
||||
description:
|
||||
|
@ -457,8 +475,6 @@ const TRANSLATIONS = {
|
|||
password: "סיסמת מופע",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "יומני אירועים",
|
||||
description: "הצג את כל הפעולות והאירועים שקורים במופע זה לצורך ניטור.",
|
||||
|
@ -469,8 +485,6 @@ const TRANSLATIONS = {
|
|||
occurred: "התרחש ב-",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "פרטיות וטיפול בנתונים",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Nome delle aree di lavoro",
|
||||
error: "errore",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Salva modifiche",
|
||||
previous: "Pagina precedente",
|
||||
next: "Pagina successiva",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Impostazioni istanza",
|
||||
system: "Impostazioni generali",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Contatta il Supporto",
|
||||
"browser-extension": "Estensione del browser",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Benvenuto in",
|
||||
|
@ -65,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Torna al Login",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Benvenuti in AnythingLLM, AnythingLLM è uno strumento di intelligenza artificiale open source di Mintplex Labs che trasforma qualsiasi cosa in un chatbot addestrato con cui puoi effettuare query e chattare. AnythingLLM è un software BYOK (bring-your-own-keys), quindi non ci sono abbonamenti, commissioni o costi per questo software al di fuori dei servizi che vuoi utilizzare.",
|
||||
|
@ -87,13 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Metti una stella su GitHub",
|
||||
contact: "Contatta Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Nuova area di lavoro",
|
||||
placeholder: "La mia area di lavoro",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Impostazioni generali",
|
||||
chat: "Impostazioni Chat",
|
||||
|
@ -101,8 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Membri",
|
||||
agent: "Configurazione dell'agente",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Contatore dei vettori",
|
||||
|
@ -139,8 +181,6 @@ const TRANSLATIONS = {
|
|||
"area di lavoro. Verranno rimossi tutti gli embeddings vettoriali nel tuo database vettoriale.\n\nI file sorgente originali rimarranno intatti. Questa azione è irreversibile.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "LLM Provider dell'area di lavoro",
|
||||
|
@ -198,8 +238,6 @@ const TRANSLATIONS = {
|
|||
hint: "La maggior parte degli LLM ha vari intervalli accettabili di valori validi. Consulta il tuo fornitore LLM per queste informazioni.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Identificatore del database vettoriale",
|
||||
snippets: {
|
||||
|
@ -228,8 +266,6 @@ const TRANSLATIONS = {
|
|||
"Il database vettoriale dell'area di lavoro è stato reimpostato!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"Le prestazioni degli LLM che non supportano esplicitamente la chiamata degli strumenti dipendono in larga misura dalle capacità e dalla precisione del modello. Alcune capacità potrebbero essere limitate o non funzionali.",
|
||||
|
@ -249,7 +285,6 @@ const TRANSLATIONS = {
|
|||
"Il modello LLM specifico che verrà utilizzato per l'agente @agent di quest'area di lavoro.",
|
||||
wait: "-- in attesa dei modelli --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Abilità predefinite dell'agente",
|
||||
description:
|
||||
|
@ -288,8 +323,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Chat dell'area di lavoro",
|
||||
description:
|
||||
|
@ -304,8 +337,6 @@ const TRANSLATIONS = {
|
|||
at: "Inviato a",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Aspetto",
|
||||
description:
|
||||
|
@ -339,8 +370,6 @@ const TRANSLATIONS = {
|
|||
link: "Collegamento",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "Chiavi API",
|
||||
description:
|
||||
|
@ -353,14 +382,12 @@ const TRANSLATIONS = {
|
|||
created: "Creato",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "Preferenza LLM",
|
||||
description:
|
||||
"Queste sono le credenziali e le impostazioni per il tuo provider di chat e embedding LLM preferito. È importante che queste chiavi siano aggiornate e corrette, altrimenti AnythingLLM non funzionerà correttamente.",
|
||||
provider: "Provider LLM",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Preferenza del modello di trascrizione",
|
||||
description:
|
||||
|
@ -373,7 +400,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"Il modello integrato verrà scaricato automaticamente al primo utilizzo.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Preferenza di embedding",
|
||||
"desc-start":
|
||||
|
@ -386,7 +412,6 @@ const TRANSLATIONS = {
|
|||
"Non è richiesta alcuna configurazione quando si utilizza il motore di embedding nativo di AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Preferenze di suddivisione e suddivisione in blocchi del testo",
|
||||
"desc-start":
|
||||
|
@ -408,8 +433,6 @@ const TRANSLATIONS = {
|
|||
"Questa è la sovrapposizione massima di caratteri che si verifica durante la suddivisione in blocchi tra due porzioni di testo adiacenti.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Database vettoriale",
|
||||
description:
|
||||
|
@ -419,8 +442,6 @@ const TRANSLATIONS = {
|
|||
description: "Non è richiesta alcuna configurazione per LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Widget di chat incorporabili",
|
||||
description:
|
||||
|
@ -432,7 +453,6 @@ const TRANSLATIONS = {
|
|||
Active: "Domini attivi",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Chat incorporate",
|
||||
export: "Esporta",
|
||||
|
@ -446,7 +466,6 @@ const TRANSLATIONS = {
|
|||
at: "Inviato a",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Modalità multi-utente",
|
||||
description:
|
||||
|
@ -471,8 +490,6 @@ const TRANSLATIONS = {
|
|||
password: "Password istanza",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Registro eventi",
|
||||
description:
|
||||
|
@ -484,8 +501,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Si è verificato alle",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Privacy e gestione dei dati",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "워크스페이스 이름",
|
||||
error: "오류",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "저장",
|
||||
previous: "이전",
|
||||
next: "다음",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "인스턴스 설정",
|
||||
system: "일반 설정",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "지원팀 연락",
|
||||
"browser-extension": "브라우저 확장 프로그램",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "웰컴!",
|
||||
|
@ -64,13 +112,10 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "로그인으로 돌아가기",
|
||||
},
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "새 워크스페이스",
|
||||
placeholder: "내 워크스페이스",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "일반 설정",
|
||||
chat: "채팅 설정",
|
||||
|
@ -78,7 +123,6 @@ const TRANSLATIONS = {
|
|||
members: "구성원",
|
||||
agent: "에이전트 구성",
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"AnythingLLM에 오신 것을 환영합니다. AnythingLLM은 Mintplex Labs에서 개발한 오픈 소스 AI 도구로, 어떤 것이든 훈련된 챗봇으로 변환하여 쿼리하고 대화할 수 있습니다. AnythingLLM은 BYOK(Bring Your Own Key) 소프트웨어이므로 사용하려는 서비스 외에는 구독료나 기타 비용이 없습니다.",
|
||||
|
@ -100,8 +144,6 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "GitHub에 별표 달기",
|
||||
contact: "Mintplex Labs에 연락하기",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "벡터 수",
|
||||
|
@ -135,8 +177,6 @@ const TRANSLATIONS = {
|
|||
"워크스페이스 전체를 삭제합니다. 이 작업은 벡터 데이터베이스에 있는 모든 벡터 임베딩을 제거합니다.\n\n원본 소스 파일은 그대로 유지됩니다. 이 작업은 되돌릴 수 없습니다.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "워크스페이스 LLM 제공자",
|
||||
|
@ -191,8 +231,6 @@ const TRANSLATIONS = {
|
|||
hint: "대부분의 LLM은 유효한 값의 다양한 허용 범위를 가지고 있습니다. 해당 정보는 LLM 제공자에게 문의하세요.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "벡터 데이터베이스 식별자",
|
||||
snippets: {
|
||||
|
@ -219,8 +257,6 @@ const TRANSLATIONS = {
|
|||
success: "워크스페이스 벡터 데이터베이스가 재설정되었습니다!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"도구 호출을 명시적으로 지원하지 않는 LLM의 성능은 모델의 기능과 정확도에 크게 좌우됩니다. 일부 기능은 제한되거나 작동하지 않을 수 있습니다.",
|
||||
|
@ -240,7 +276,6 @@ const TRANSLATIONS = {
|
|||
"이 워크스페이스의 @agent 에이전트에 사용할 특정 LLM 모델입니다.",
|
||||
wait: "-- 모델 기다리는 중 --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "기본 에이전트 스킬",
|
||||
description:
|
||||
|
@ -279,8 +314,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "워크스페이스 채팅",
|
||||
description:
|
||||
|
@ -295,8 +328,6 @@ const TRANSLATIONS = {
|
|||
at: "보낸 시각",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "외관",
|
||||
description: "플랫폼의 외관 설정을 수정합니다.",
|
||||
|
@ -327,8 +358,6 @@ const TRANSLATIONS = {
|
|||
link: "링크",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API 키",
|
||||
description:
|
||||
|
@ -341,14 +370,12 @@ const TRANSLATIONS = {
|
|||
created: "생성일",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM 기본 설정",
|
||||
description:
|
||||
"이것은 채팅과 임베딩을 하기 위한 선호하는 LLM 제공자의 인증입니다. 이 키가 현재 활성 상태이고 정확해야 AnythingLLM이 제대로 작동합니다.",
|
||||
provider: "LLM 제공자",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "텍스트 변환 모델 기본 설정",
|
||||
description:
|
||||
|
@ -359,7 +386,6 @@ const TRANSLATIONS = {
|
|||
"warn-recommend": "최소 2GB RAM과 10Mb 보다 작은 파일 업로드를 권장합니다.",
|
||||
"warn-end": "내장된 모델은 첫 번째 사용 시 자동으로 다운로드됩니다.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "임베딩 기본 설정",
|
||||
"desc-start":
|
||||
|
@ -372,7 +398,6 @@ const TRANSLATIONS = {
|
|||
"AnythingLLM의 기본 임베딩 엔진을 사용할 때는 설정이 필요하지 않습니다.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "텍스트 분할 및 청킹 기본 설정",
|
||||
"desc-start":
|
||||
|
@ -387,15 +412,12 @@ const TRANSLATIONS = {
|
|||
description: "단일 벡터에 들어갈 수 있는 최대 문자 길이입니다.",
|
||||
recommend: "임베드 모델 최대 길이는",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "텍스트 청크 겹침",
|
||||
description:
|
||||
"청킹 동안 두 인접 텍스트 청크 간에 겹칠 수 있는 최대 문자 수입니다.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "벡터 데이터베이스",
|
||||
description:
|
||||
|
@ -405,8 +427,6 @@ const TRANSLATIONS = {
|
|||
description: "LanceDB를 선택하면 설정이 필요 없습니다.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "임베드 가능한 채팅 위젯",
|
||||
description:
|
||||
|
@ -418,7 +438,6 @@ const TRANSLATIONS = {
|
|||
Active: "활성 도메인",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "임베드 채팅",
|
||||
export: "내보내기",
|
||||
|
@ -431,7 +450,6 @@ const TRANSLATIONS = {
|
|||
at: "보낸 시각",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "다중 사용자 모드",
|
||||
description:
|
||||
|
@ -456,8 +474,6 @@ const TRANSLATIONS = {
|
|||
password: "인스턴스 비밀번호",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "이벤트 로그",
|
||||
description:
|
||||
|
@ -469,8 +485,6 @@ const TRANSLATIONS = {
|
|||
occurred: "발생 시각",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "개인정보와 데이터 처리",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Werkruimten Naam",
|
||||
error: "fout",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Wijzigingen opslaan",
|
||||
previous: "Vorige pagina",
|
||||
next: "Volgende pagina",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Instelling Instanties",
|
||||
system: "Algemene Instellingen",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Contact Ondersteuning",
|
||||
"browser-extension": "Browser Extensie",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Welkom bij",
|
||||
|
@ -65,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Terug naar Inloggen",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Welkom bij AnythingLLM, AnythingLLM is een open-source AI-tool van Mintplex Labs die alles omzet in een getrainde chatbot waarmee je kunt vragen en chatten. AnythingLLM is een BYOK (bring-your-own-keys) software, dus er is geen abonnement, vergoeding of kosten voor deze software buiten de diensten die je ermee wilt gebruiken.",
|
||||
|
@ -87,13 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Ster op GitHub",
|
||||
contact: "Contact Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Nieuwe Werkruimte",
|
||||
placeholder: "Mijn Werkruimte",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Algemene Instellingen",
|
||||
chat: "Chat Instellingen",
|
||||
|
@ -101,8 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Leden",
|
||||
agent: "Agent Configuratie",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Vector Teller",
|
||||
|
@ -138,8 +180,6 @@ const TRANSLATIONS = {
|
|||
"werkruimte te verwijderen. Dit zal alle vector inbeddingen in je vector database verwijderen.\n\nDe originele bronbestanden blijven onaangetast. Deze actie is onomkeerbaar.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Werkruimte LLM Provider",
|
||||
|
@ -197,8 +237,6 @@ const TRANSLATIONS = {
|
|||
hint: "De meeste LLM's hebben verschillende acceptabele reeksen van geldige waarden. Raadpleeg je LLM-provider voor die informatie.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Vector database-identificator",
|
||||
snippets: {
|
||||
|
@ -225,8 +263,6 @@ const TRANSLATIONS = {
|
|||
success: "Werkruimte vector database is gereset!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"De prestaties van LLM's die geen tool-aanroep expliciet ondersteunen, zijn sterk afhankelijk van de capaciteiten en nauwkeurigheid van het model. Sommige vaardigheden kunnen beperkt of niet-functioneel zijn.",
|
||||
|
@ -246,7 +282,6 @@ const TRANSLATIONS = {
|
|||
"Het specifieke LLM-model dat voor het @agent-agent van deze werkruimte zal worden gebruikt.",
|
||||
wait: "-- wachten op modellen --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Standaard agentvaardigheden",
|
||||
description:
|
||||
|
@ -285,8 +320,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Werkruimte Chats",
|
||||
description:
|
||||
|
@ -301,8 +334,6 @@ const TRANSLATIONS = {
|
|||
at: "Verzonden Om",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Verschijning",
|
||||
description: "Pas de verschijningsinstellingen van je platform aan.",
|
||||
|
@ -334,8 +365,6 @@ const TRANSLATIONS = {
|
|||
link: "Link",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API-sleutels",
|
||||
description:
|
||||
|
@ -348,14 +377,12 @@ const TRANSLATIONS = {
|
|||
created: "Aangemaakt",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM Voorkeur",
|
||||
description:
|
||||
"Dit zijn de inloggegevens en instellingen voor je voorkeurs LLM-chat & inbeddingprovider. Het is belangrijk dat deze sleutels actueel en correct zijn, anders zal AnythingLLM niet goed werken.",
|
||||
provider: "LLM Provider",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Transcriptiemodel Voorkeur",
|
||||
description:
|
||||
|
@ -368,7 +395,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"Het ingebouwde model wordt automatisch gedownload bij het eerste gebruik.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Inbedding Voorkeur",
|
||||
"desc-start":
|
||||
|
@ -381,7 +407,6 @@ const TRANSLATIONS = {
|
|||
"Er is geen instelling vereist bij gebruik van de ingebouwde inbeddingengine van AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Tekst Splitsen & Chunking Voorkeuren",
|
||||
"desc-start":
|
||||
|
@ -397,15 +422,12 @@ const TRANSLATIONS = {
|
|||
"Dit is de maximale lengte van tekens die aanwezig kan zijn in een enkele vector.",
|
||||
recommend: "Inbed model maximale lengte is",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Tekst Chunk Overlap",
|
||||
description:
|
||||
"Dit is de maximale overlap van tekens die optreedt tijdens het chunking tussen twee aangrenzende tekstchunks.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Vector Database",
|
||||
description:
|
||||
|
@ -415,8 +437,6 @@ const TRANSLATIONS = {
|
|||
description: "Er is geen configuratie nodig voor LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Inbedbare Chat Widgets",
|
||||
description:
|
||||
|
@ -428,7 +448,6 @@ const TRANSLATIONS = {
|
|||
Active: "Actieve Domeinen",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Inbedding Chats",
|
||||
export: "Exporteren",
|
||||
|
@ -442,7 +461,6 @@ const TRANSLATIONS = {
|
|||
at: "Verzonden Om",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Multi-Gebruikersmodus",
|
||||
description:
|
||||
|
@ -467,8 +485,6 @@ const TRANSLATIONS = {
|
|||
password: "Instantie wachtwoord",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Gebeurtenislogboeken",
|
||||
description:
|
||||
|
@ -480,8 +496,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Opgetreden Op",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Privacy & Gegevensverwerking",
|
||||
description:
|
||||
|
|
157
frontend/src/locales/normalizeEn.mjs
Normal file
157
frontend/src/locales/normalizeEn.mjs
Normal file
|
@ -0,0 +1,157 @@
|
|||
// This script is used to normalize the translations files to ensure they are all the same.
|
||||
// This will take the en file and compare it to all other files and ensure they are all the same.
|
||||
// If a non-en file is missing a key, it will be added to the file and set to null
|
||||
import { resources } from "./resources.js";
|
||||
import fs from "fs";
|
||||
const languageNames = new Intl.DisplayNames(Object.keys(resources), {
|
||||
type: "language",
|
||||
});
|
||||
|
||||
function langDisplayName(lang) {
|
||||
return languageNames.of(lang);
|
||||
}
|
||||
|
||||
function compareStructures(lang, a, b, subdir = null) {
|
||||
//if a and b aren't the same type, they can't be equal
|
||||
if (typeof a !== typeof b && a !== null && b !== null) {
|
||||
console.log("Invalid type comparison", [
|
||||
{
|
||||
lang,
|
||||
a: typeof a,
|
||||
b: typeof b,
|
||||
values: {
|
||||
a,
|
||||
b,
|
||||
},
|
||||
...(!!subdir ? { subdir } : {}),
|
||||
},
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need the truthy guard because
|
||||
// typeof null === 'object'
|
||||
if (a && typeof a === "object") {
|
||||
var keysA = Object.keys(a).sort(),
|
||||
keysB = Object.keys(b).sort();
|
||||
|
||||
//if a and b are objects with different no of keys, unequal
|
||||
if (keysA.length !== keysB.length) {
|
||||
console.log("Keys are missing!", {
|
||||
[lang]: keysA,
|
||||
en: keysB,
|
||||
...(!!subdir ? { subdir } : {}),
|
||||
diff: {
|
||||
added: keysB.filter((key) => !keysA.includes(key)),
|
||||
removed: keysA.filter((key) => !keysB.includes(key)),
|
||||
},
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
//if keys aren't all the same, unequal
|
||||
if (
|
||||
!keysA.every(function (k, i) {
|
||||
return k === keysB[i];
|
||||
})
|
||||
) {
|
||||
console.log("Keys are not equal!", {
|
||||
[lang]: keysA,
|
||||
en: keysB,
|
||||
...(!!subdir ? { subdir } : {}),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
//recurse on the values for each key
|
||||
return keysA.every(function (key) {
|
||||
//if we made it here, they have identical keys
|
||||
return compareStructures(lang, a[key], b[key], key);
|
||||
});
|
||||
|
||||
//for primitives just ignore since we don't check values.
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTranslations(lang, source, target, subdir = null) {
|
||||
// Handle primitives - if target exists, keep it, otherwise set null
|
||||
if (!source || typeof source !== "object") {
|
||||
return target ?? null;
|
||||
}
|
||||
|
||||
// Handle objects
|
||||
const normalized = target && typeof target === "object" ? { ...target } : {};
|
||||
|
||||
// Add all keys from source (English), setting to null if missing
|
||||
for (const key of Object.keys(source)) {
|
||||
normalized[key] = normalizeTranslations(
|
||||
lang,
|
||||
source[key],
|
||||
normalized[key],
|
||||
key
|
||||
);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function ISOToFilename(lang) {
|
||||
const ISO_TO_FILENAME = {
|
||||
"zh-tw": "zh_TW",
|
||||
pt: "pt_BR",
|
||||
vi: "vn",
|
||||
};
|
||||
return ISO_TO_FILENAME[lang] || lang.replace("-", "_");
|
||||
}
|
||||
|
||||
const failed = [];
|
||||
const TRANSLATIONS = {};
|
||||
for (const [lang, { common }] of Object.entries(resources)) {
|
||||
TRANSLATIONS[lang] = common;
|
||||
}
|
||||
|
||||
const PRIMARY = { ...TRANSLATIONS["en"] };
|
||||
delete TRANSLATIONS["en"];
|
||||
|
||||
console.log(
|
||||
`The following translation files will be normalized against the English file: [${Object.keys(
|
||||
TRANSLATIONS
|
||||
).join(",")}]`
|
||||
);
|
||||
|
||||
// Normalize each non-English translation
|
||||
for (const [lang, translations] of Object.entries(TRANSLATIONS)) {
|
||||
const normalized = normalizeTranslations(lang, PRIMARY, translations);
|
||||
|
||||
// Update the translations in resources
|
||||
resources[lang].common = normalized;
|
||||
|
||||
// Verify the structure matches
|
||||
const passed = compareStructures(lang, normalized, PRIMARY);
|
||||
console.log(`${langDisplayName(lang)} (${lang}): ${passed ? "✅" : "❌"}`);
|
||||
!passed && failed.push(lang);
|
||||
|
||||
const langFilename = ISOToFilename(lang);
|
||||
fs.writeFileSync(
|
||||
`./${langFilename}/common.js`,
|
||||
`// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = ${JSON.stringify(normalized, null, 2)}
|
||||
|
||||
export default TRANSLATIONS;`
|
||||
);
|
||||
}
|
||||
|
||||
if (failed.length !== 0) {
|
||||
throw new Error(
|
||||
`Error verifying normalized translations. Please check the logs.`,
|
||||
failed
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`👍 All translation files have been normalized to match the English schema!`
|
||||
);
|
||||
|
||||
process.exit(0);
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Nome dos Workspaces",
|
||||
error: "erro",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Salvar alterações",
|
||||
previous: "Página Anterior",
|
||||
next: "Próxima Página",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Configurações da Instância",
|
||||
system: "Configurações Gerais",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Contato com Suporte",
|
||||
"browser-extension": "Extensão do navegador",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Bem-vindo ao",
|
||||
|
@ -65,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Voltar ao Login",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Bem-vindo ao AnythingLLM, AnythingLLM é uma ferramenta de IA de código aberto da Mintplex Labs que transforma qualquer coisa em um chatbot treinado que você pode consultar e conversar. AnythingLLM é um software BYOK (bring-your-own-keys | traga suas próprias chaves), portanto, não há assinatura, taxa ou cobranças para este software fora dos serviços que você deseja usar com ele.",
|
||||
|
@ -87,13 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Dar estrela no GitHub",
|
||||
contact: "Contato Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Novo Workspace",
|
||||
placeholder: "Meu Workspace",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Configurações Gerais",
|
||||
chat: "Configurações de Chat",
|
||||
|
@ -101,8 +145,6 @@ const TRANSLATIONS = {
|
|||
members: "Membros",
|
||||
agent: "Configuração do Agente",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Contagem de Vetores",
|
||||
|
@ -138,8 +180,6 @@ const TRANSLATIONS = {
|
|||
"workspace. Isso removerá todas as incorporações vetoriais no seu banco de dados vetorial.\n\nOs arquivos de origem originais permanecerão intactos. Esta ação é irreversível.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Provedor de LLM do Workspace",
|
||||
|
@ -197,8 +237,6 @@ const TRANSLATIONS = {
|
|||
hint: "A maioria dos LLMs tem vários intervalos aceitáveis de valores válidos. Consulte seu provedor de LLM para essa informação.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Identificador do Banco de Dados Vetorial",
|
||||
snippets: {
|
||||
|
@ -226,8 +264,6 @@ const TRANSLATIONS = {
|
|||
"O banco de dados vetorial do workspace foi redefinido com sucesso!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"O desempenho dos LLMs que não suportam explicitamente a chamada de ferramentas depende muito das capacidades e da precisão do modelo. Algumas habilidades podem ser limitadas ou não funcionais.",
|
||||
|
@ -247,7 +283,6 @@ const TRANSLATIONS = {
|
|||
"O modelo de LLM específico que será usado para o agente @agent deste workspace.",
|
||||
wait: "-- aguardando modelos --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Habilidades padrão do agente",
|
||||
description:
|
||||
|
@ -286,8 +321,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Chats do Workspace",
|
||||
description:
|
||||
|
@ -302,8 +335,6 @@ const TRANSLATIONS = {
|
|||
at: "Enviado Em",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Aparência",
|
||||
description: "Personalize as configurações de aparência da sua plataforma.",
|
||||
|
@ -336,8 +367,6 @@ const TRANSLATIONS = {
|
|||
link: "Link",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "Chaves API",
|
||||
description:
|
||||
|
@ -350,14 +379,12 @@ const TRANSLATIONS = {
|
|||
created: "Criado",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "Preferência de LLM",
|
||||
description:
|
||||
"Estas são as credenciais e configurações para seu provedor preferido de chat e incorporação de LLM. É importante que essas chaves estejam atualizadas e corretas, caso contrário, o AnythingLLM não funcionará corretamente.",
|
||||
provider: "Provedor de LLM",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Preferência de Modelo de Transcrição",
|
||||
description:
|
||||
|
@ -370,7 +397,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"O modelo embutido será baixado automaticamente no primeiro uso.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Preferência de Incorporação",
|
||||
"desc-start":
|
||||
|
@ -383,7 +409,6 @@ const TRANSLATIONS = {
|
|||
"Não é necessária configuração ao usar o mecanismo de incorporação nativo do AnythingLLM.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Preferências de Divisão e Fragmentação de Texto",
|
||||
"desc-start":
|
||||
|
@ -399,15 +424,12 @@ const TRANSLATIONS = {
|
|||
"Este é o comprimento máximo de caracteres que pode estar presente em um único vetor.",
|
||||
recommend: "O comprimento máximo do modelo de incorporação é",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Sobreposição de Fragmento de Texto",
|
||||
description:
|
||||
"Esta é a sobreposição máxima de caracteres que ocorre durante a fragmentação entre dois fragmentos de texto adjacentes.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Banco de Dados Vetorial",
|
||||
description:
|
||||
|
@ -417,8 +439,6 @@ const TRANSLATIONS = {
|
|||
description: "Não há configuração necessária para o LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Widgets de Chat Incorporáveis",
|
||||
description:
|
||||
|
@ -430,7 +450,6 @@ const TRANSLATIONS = {
|
|||
Active: "Domínios Ativos",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Incorporar Chats",
|
||||
export: "Exportar",
|
||||
|
@ -444,7 +463,6 @@ const TRANSLATIONS = {
|
|||
at: "Enviado Em",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Modo Multiusuário",
|
||||
description:
|
||||
|
@ -469,8 +487,6 @@ const TRANSLATIONS = {
|
|||
password: "Senha da instância",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Logs de Eventos",
|
||||
description:
|
||||
|
@ -482,8 +498,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Ocorreu Em",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Privacidade e Tratamento de Dados",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Имя рабочих пространств",
|
||||
error: "ошибка",
|
||||
|
@ -9,8 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Сохранить изменения",
|
||||
previous: "Предыдущая страница",
|
||||
next: "Следующая страница",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
settings: {
|
||||
title: "Настройки экземпляра",
|
||||
system: "Системные настройки",
|
||||
|
@ -39,7 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "联系支持Связаться с Поддержкой",
|
||||
"browser-extension": "Расширение браузера",
|
||||
},
|
||||
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Добро пожаловать в",
|
||||
|
@ -63,7 +113,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Вернуться к входу",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Добро пожаловать в AnythingLLM, открытый инструмент искусственного интеллекта от Mintplex Labs, который превращает что угодно в обученный чат-бот, с которым вы можете общаться и задавать вопросы. AnythingLLM - это ПО BYOK (принеси свои собственные ключи), поэтому за использование этого ПО нет подписки, платы или других сборов, кроме тех, что вы хотите использовать.",
|
||||
|
@ -85,12 +134,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Звезда на GitHub",
|
||||
contact: "Связаться с Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Новая Рабочая Область",
|
||||
placeholder: "Моя Рабочая Область",
|
||||
},
|
||||
|
||||
"workspaces—settings": {
|
||||
general: "Общие настройки",
|
||||
chat: "Настройки чата",
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Çalışma Alanları Adı",
|
||||
error: "hata",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Değişiklikleri Kaydet",
|
||||
previous: "Önceki Sayfa",
|
||||
next: "Sonraki Sayfa",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Instance Ayarları",
|
||||
system: "Genel Ayarlar",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Destekle İletişime Geçin",
|
||||
"browser-extension": "Tarayıcı Uzantısı",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Hoş geldiniz",
|
||||
|
@ -64,7 +112,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Girişe Geri Dön",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"AnythingLLM'e hoş geldiniz. AnythingLLM, Mintplex Labs tarafından geliştirilen açık kaynaklı bir yapay zeka aracıdır ve her şeyi, sorgulayabileceğiniz ve sohbet edebileceğiniz eğitimli bir chatbota dönüştürür. AnythingLLM, BYOK (kendi anahtarlarınızı getirin) yazılımıdır; bu nedenle, kullanmak istediğiniz hizmetler dışında herhangi bir abonelik, ücret ya da ek masraf yoktur.",
|
||||
|
@ -86,13 +133,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "GitHub'da Yıldız Verin",
|
||||
contact: "Mintplex Labs ile İletişime Geçin",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Yeni Çalışma Alanı",
|
||||
placeholder: "Benim Çalışma Alanım",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Genel Ayarlar",
|
||||
chat: "Sohbet Ayarları",
|
||||
|
@ -100,8 +144,6 @@ const TRANSLATIONS = {
|
|||
members: "Üyeler",
|
||||
agent: "Ajan Yapılandırması",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Vektör Sayısı",
|
||||
|
@ -138,8 +180,6 @@ const TRANSLATIONS = {
|
|||
". Bu, vektör veritabanınızdaki tüm vektör gömme verilerini kaldıracaktır.\n\nOrijinal kaynak dosyalar etkilenmeyecektir. Bu işlem geri alınamaz.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Çalışma Alanı LLM Sağlayıcısı",
|
||||
|
@ -197,8 +237,6 @@ const TRANSLATIONS = {
|
|||
hint: "Çoğu LLM'in farklı kabul edilebilir değer aralıkları vardır. Ayrıntılar için LLM sağlayıcınıza danışın.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Vektör veritabanı tanımlayıcısı",
|
||||
snippets: {
|
||||
|
@ -225,8 +263,6 @@ const TRANSLATIONS = {
|
|||
success: "Çalışma alanının vektör veritabanı sıfırlandı!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"Araç çağırmayı açıkça desteklemeyen LLM'lerin performansı, modelin yetenekleri ve doğruluğuna büyük ölçüde bağlıdır. Bazı beceriler kısıtlı veya işlevsiz olabilir.",
|
||||
|
@ -246,7 +282,6 @@ const TRANSLATIONS = {
|
|||
"Bu çalışma alanındaki @agent ajanı için kullanılacak spesifik LLM modeli.",
|
||||
wait: "-- modeller bekleniyor --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Varsayılan ajan becerileri",
|
||||
description:
|
||||
|
@ -285,8 +320,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Çalışma Alanı Sohbetleri",
|
||||
description:
|
||||
|
@ -301,8 +334,6 @@ const TRANSLATIONS = {
|
|||
at: "Gönderilme Zamanı",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Görünüm",
|
||||
description: "Platformunuzun görünüm ayarlarını özelleştirin.",
|
||||
|
@ -335,8 +366,6 @@ const TRANSLATIONS = {
|
|||
link: "Bağlantı",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API Anahtarları",
|
||||
description:
|
||||
|
@ -349,14 +378,12 @@ const TRANSLATIONS = {
|
|||
created: "Oluşturulma Tarihi",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM Tercihi",
|
||||
description:
|
||||
"Bu, tercih ettiğiniz LLM sohbet ve gömme sağlayıcısının kimlik bilgileri ile ayarlarıdır. Bu anahtarların güncel ve doğru olması önemlidir; aksi takdirde AnythingLLM doğru çalışmayacaktır.",
|
||||
provider: "LLM Sağlayıcısı",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Transkripsiyon Model Tercihi",
|
||||
description:
|
||||
|
@ -369,7 +396,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"Yerleşik model, ilk kullanımda otomatik olarak indirilecektir.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Gömme (Embedding) Tercihi",
|
||||
"desc-start":
|
||||
|
@ -382,8 +408,6 @@ const TRANSLATIONS = {
|
|||
"AnythingLLM'nin yerel gömme motoru kullanıldığında ek bir kurulum gerekmez.",
|
||||
},
|
||||
},
|
||||
|
||||
// Text Splitting & Chunking Preferences
|
||||
text: {
|
||||
title: "Metin Bölme & Parçalama Tercihleri",
|
||||
"desc-start":
|
||||
|
@ -405,8 +429,6 @@ const TRANSLATIONS = {
|
|||
"İki bitişik metin parçası arasındaki, parçalama sırasında oluşabilecek maksimum karakter örtüşme miktarını belirtir.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Vektör Veritabanı",
|
||||
description:
|
||||
|
@ -416,8 +438,6 @@ const TRANSLATIONS = {
|
|||
description: "LanceDB için ek bir yapılandırma gerekmez.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Gömülebilir Sohbet Widget'ları",
|
||||
description:
|
||||
|
@ -429,8 +449,6 @@ const TRANSLATIONS = {
|
|||
Active: "Aktif Alan Adları",
|
||||
},
|
||||
},
|
||||
|
||||
// Embed Chats
|
||||
"embed-chats": {
|
||||
title: "Gömme Sohbetler",
|
||||
export: "Dışa Aktar",
|
||||
|
@ -444,8 +462,6 @@ const TRANSLATIONS = {
|
|||
at: "Gönderilme Zamanı",
|
||||
},
|
||||
},
|
||||
|
||||
// Multi-User Mode
|
||||
multi: {
|
||||
title: "Çoklu Kullanıcı Modu",
|
||||
description:
|
||||
|
@ -470,8 +486,6 @@ const TRANSLATIONS = {
|
|||
password: "Örnek Şifresi",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Olay Kayıtları",
|
||||
description:
|
||||
|
@ -483,8 +497,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Gerçekleşme Zamanı",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Gizlilik & Veri İşleme",
|
||||
description:
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
survey: {
|
||||
email: null,
|
||||
useCase: null,
|
||||
useCaseWork: null,
|
||||
useCasePersonal: null,
|
||||
useCaseOther: null,
|
||||
comment: null,
|
||||
commentPlaceholder: null,
|
||||
skip: null,
|
||||
thankYou: null,
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
home: {
|
||||
title: null,
|
||||
getStarted: null,
|
||||
},
|
||||
llm: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
userSetup: {
|
||||
title: null,
|
||||
description: null,
|
||||
howManyUsers: null,
|
||||
justMe: null,
|
||||
myTeam: null,
|
||||
instancePassword: null,
|
||||
setPassword: null,
|
||||
passwordReq: null,
|
||||
passwordWarn: null,
|
||||
adminUsername: null,
|
||||
adminUsernameReq: null,
|
||||
adminPassword: null,
|
||||
adminPasswordReq: null,
|
||||
teamHint: null,
|
||||
},
|
||||
data: {
|
||||
title: null,
|
||||
description: null,
|
||||
settingsHint: null,
|
||||
},
|
||||
workspace: {
|
||||
title: null,
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "Tên không gian làm việc",
|
||||
error: "Lỗi",
|
||||
|
@ -9,9 +58,10 @@ const TRANSLATIONS = {
|
|||
save: "Lưu thay đổi",
|
||||
previous: "Trang trước",
|
||||
next: "Trang tiếp theo",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Cài đặt hệ thống",
|
||||
system: "Cài đặt chung",
|
||||
|
@ -40,8 +90,6 @@ const TRANSLATIONS = {
|
|||
contact: "Liên hệ hỗ trợ",
|
||||
"browser-extension": "Tiện ích trình duyệt",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Chào mừng đến với",
|
||||
|
@ -64,7 +112,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "Back to Đăng nhập",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"Chào mừng đến với AnythingLLM, AnythingLLM is an open-source AI tool by Mintplex Labs that turns anything into a trained chatbot you can query and chat with. AnythingLLM is a BYOK (bring-your-own-keys) software so there is no subscription, fee, or charges for this software outside of the services you want to use with it.",
|
||||
|
@ -86,13 +133,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "Star on GitHub",
|
||||
contact: "Contact Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "Không gian làm việc mới",
|
||||
placeholder: "Không gian làm việc của tôi",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Cài đặt chung",
|
||||
chat: "Chat Settings",
|
||||
|
@ -100,8 +144,6 @@ const TRANSLATIONS = {
|
|||
members: "Members",
|
||||
agent: "Agent Configuration",
|
||||
},
|
||||
|
||||
// General Giao diện
|
||||
general: {
|
||||
vector: {
|
||||
title: "Vector Count",
|
||||
|
@ -137,8 +179,6 @@ const TRANSLATIONS = {
|
|||
"workspace. This will remove all vector embeddings in your vector database.\n\nThe original source files will remain untouched. This action is irreversible.",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "Workspace LLM Provider",
|
||||
|
@ -196,8 +236,6 @@ const TRANSLATIONS = {
|
|||
hint: "Most LLMs have various acceptable ranges of valid values. Consult your LLM provider for that information.",
|
||||
},
|
||||
},
|
||||
|
||||
// Cơ sở dữ liệu Vector
|
||||
"vector-workspace": {
|
||||
identifier: "Vector database identifier",
|
||||
snippets: {
|
||||
|
@ -224,8 +262,6 @@ const TRANSLATIONS = {
|
|||
success: "Workspace vector database was reset!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"Performance of LLMs that do not explicitly support tool-calling is highly dependent on the model's capabilities and accuracy. Some abilities may be limited or non-functional.",
|
||||
|
@ -245,7 +281,6 @@ const TRANSLATIONS = {
|
|||
"The specific LLM model that will be used for this workspace's @agent agent.",
|
||||
wait: "-- waiting for models --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Default agent skills",
|
||||
description:
|
||||
|
@ -284,8 +319,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Hội thoại không gian làm việc
|
||||
recorded: {
|
||||
title: "Hội thoại không gian làm việc",
|
||||
description:
|
||||
|
@ -300,8 +333,6 @@ const TRANSLATIONS = {
|
|||
at: "Sent At",
|
||||
},
|
||||
},
|
||||
|
||||
// Giao diện
|
||||
appearance: {
|
||||
title: "Giao diện",
|
||||
description: "Customize the appearance settings of your platform.",
|
||||
|
@ -332,8 +363,6 @@ const TRANSLATIONS = {
|
|||
link: "Link",
|
||||
},
|
||||
},
|
||||
|
||||
// Khóa API
|
||||
api: {
|
||||
title: "Khóa API",
|
||||
description:
|
||||
|
@ -346,14 +375,12 @@ const TRANSLATIONS = {
|
|||
created: "Created",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM Preference",
|
||||
description:
|
||||
"These are the credentials and settings for your preferred LLM chat & embedding provider. Its important these keys are current and correct or else AnythingLLM will not function properly.",
|
||||
provider: "LLM Provider",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "Chuyển đổi giọng nói Model Preference",
|
||||
description:
|
||||
|
@ -366,7 +393,6 @@ const TRANSLATIONS = {
|
|||
"warn-end":
|
||||
"The built-in model will automatically download on the first use.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Tùy chọn nhúng",
|
||||
"desc-start":
|
||||
|
@ -379,7 +405,6 @@ const TRANSLATIONS = {
|
|||
"There is no set up required when using AnythingLLM's native embedding engine.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Tùy chọn chia nhỏ và tách văn bản",
|
||||
"desc-start":
|
||||
|
@ -395,15 +420,12 @@ const TRANSLATIONS = {
|
|||
"This is the maximum length of characters that can be present in a single vector.",
|
||||
recommend: "Embed model maximum length is",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "Text Chunk Overlap",
|
||||
description:
|
||||
"This is the maximum overlap of characters that occurs during chunking between two adjacent text chunks.",
|
||||
},
|
||||
},
|
||||
|
||||
// Cơ sở dữ liệu Vector
|
||||
vector: {
|
||||
title: "Cơ sở dữ liệu Vector",
|
||||
description:
|
||||
|
@ -413,8 +435,6 @@ const TRANSLATIONS = {
|
|||
description: "There is no configuration needed for LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Tiện ích hội thoại nhúng
|
||||
embeddable: {
|
||||
title: "Tiện ích hội thoại nhúng",
|
||||
description:
|
||||
|
@ -426,7 +446,6 @@ const TRANSLATIONS = {
|
|||
Active: "Active Domains",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Embed Chats",
|
||||
export: "Export",
|
||||
|
@ -440,7 +459,6 @@ const TRANSLATIONS = {
|
|||
at: "Sent At",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Multi-Người dùng Mode",
|
||||
description:
|
||||
|
@ -465,8 +483,6 @@ const TRANSLATIONS = {
|
|||
password: "Instance password",
|
||||
},
|
||||
},
|
||||
|
||||
// Nhật ký sự kiện
|
||||
event: {
|
||||
title: "Nhật ký sự kiện",
|
||||
description:
|
||||
|
@ -478,8 +494,6 @@ const TRANSLATIONS = {
|
|||
occurred: "Occurred At",
|
||||
},
|
||||
},
|
||||
|
||||
// Quyền riêng tư & Dữ liệu-Handling
|
||||
privacy: {
|
||||
title: "Quyền riêng tư & Dữ liệu-Handling",
|
||||
description:
|
||||
|
|
|
@ -1,5 +1,57 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
home: {
|
||||
title: "欢迎使用",
|
||||
getStarted: "开始",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM 偏好",
|
||||
description:
|
||||
"AnythingLLM 可以与多家 LLM 提供商合作。这将是处理聊天的服务。",
|
||||
},
|
||||
userSetup: {
|
||||
title: "用户设置",
|
||||
description: "配置你的用户设置。",
|
||||
howManyUsers: "将有多少用户使用此实例?",
|
||||
justMe: "只有我",
|
||||
myTeam: "我的团队",
|
||||
instancePassword: "实例密码",
|
||||
setPassword: "你想要设置密码吗?",
|
||||
passwordReq: "密码必须至少包含 8 个字符。",
|
||||
passwordWarn: "保存此密码很重要,因为没有恢复方法。",
|
||||
adminUsername: "管理员账户用户名",
|
||||
adminUsernameReq:
|
||||
"用户名必须至少为 6 个字符,并且只能包含小写字母、数字、下划线和连字符,不含空格。",
|
||||
adminPassword: "管理员账户密码",
|
||||
adminPasswordReq: "密码必须至少包含 8 个字符。",
|
||||
teamHint:
|
||||
"默认情况下,你将是唯一的管理员。完成入职后,你可以创建和邀请其他人成为用户或管理员。不要丢失你的密码,因为只有管理员可以重置密码。",
|
||||
},
|
||||
data: {
|
||||
title: "数据处理与隐私",
|
||||
description: "我们致力于在涉及你的个人数据时提供透明和控制。",
|
||||
settingsHint: "这些设置可以随时在设置中重新配置。",
|
||||
},
|
||||
survey: {
|
||||
title: "欢迎使用 AnythingLLM",
|
||||
description: "帮助我们为你的需求打造 AnythingLLM。可选。",
|
||||
email: "你的电子邮件是什么?",
|
||||
useCase: "你将如何使用 AnythingLLM?",
|
||||
useCaseWork: "用于工作",
|
||||
useCasePersonal: "用于个人使用",
|
||||
useCaseOther: "其他",
|
||||
comment: "你是如何听说 AnythingLLM 的?",
|
||||
commentPlaceholder:
|
||||
"Reddit,Twitter,GitHub,YouTube 等 - 让我们知道你是如何找到我们的!",
|
||||
skip: "跳过调查",
|
||||
thankYou: "感谢你的反馈!",
|
||||
},
|
||||
workspace: {
|
||||
title: "创建你的第一个工作区",
|
||||
description: "创建你的第一个工作区并开始使用 AnythingLLM。",
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "工作区名称",
|
||||
error: "错误",
|
||||
|
@ -10,16 +62,17 @@ const TRANSLATIONS = {
|
|||
saving: "保存中...",
|
||||
previous: "上一页",
|
||||
next: "下一页",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "设置",
|
||||
system: "系统",
|
||||
invites: "邀请",
|
||||
users: "用户",
|
||||
workspaces: "工作区",
|
||||
"workspace-chats": "对话历史记录", // "workspace-chats" should be "对话历史记录", means "chat history",or "chat history records"
|
||||
"workspace-chats": "对话历史记录",
|
||||
customization: "外观",
|
||||
"api-keys": "API 密钥",
|
||||
llm: "LLM 首选项",
|
||||
|
@ -41,8 +94,6 @@ const TRANSLATIONS = {
|
|||
contact: "联系支持",
|
||||
"browser-extension": "浏览器扩展",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "欢迎!",
|
||||
|
@ -59,40 +110,36 @@ const TRANSLATIONS = {
|
|||
},
|
||||
"password-reset": {
|
||||
title: "重置密码",
|
||||
description: "请提供以下必要信息以重置您的密码。",
|
||||
description: "请提供以下必要信息以重置你的密码。",
|
||||
"recovery-codes": "恢复代码",
|
||||
"recovery-code": "恢复代码 {{index}}",
|
||||
"back-to-login": "返回登录",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"欢迎使用 AnythingLLM,这是由 Mintplex Labs 开发的开源 AI 工具,可以将任何东西转换为您可以查询和聊天的训练有素的聊天机器人。AnythingLLM 是一款 BYOK(自带密钥)软件,因此除了您想使用的服务外,此软件不收取订阅费、费用或其他费用。",
|
||||
"欢迎使用 AnythingLLM,这是由 Mintplex Labs 开发的开源 AI 工具,可以将任何东西转换为你可以查询和聊天的训练有素的聊天机器人。AnythingLLM 是一款 BYOK(自带密钥)软件,因此除了你想使用的服务外,此软件不收取订阅费、费用或其他费用。",
|
||||
part2:
|
||||
"AnythingLLM 是将强大的 AI 产品(如 OpenAi、GPT-4、LangChain、PineconeDB、ChromaDB 等)整合在一个整洁的包中而无需繁琐操作的最简单方法,可以将您的生产力提高 100 倍。",
|
||||
"AnythingLLM 是将强大的 AI 产品(如 OpenAi、GPT-4、LangChain、PineconeDB、ChromaDB 等)整合在一个整洁的包中而无需繁琐操作的最简单方法,可以将你的生产力提高 100 倍。",
|
||||
part3:
|
||||
"AnythingLLM 可以完全在您的本地计算机上运行,几乎没有开销,您甚至不会注意到它的存在!无需 GPU。也可以进行云端和本地安装。\nAI 工具生态系统每天都在变得更强大。AnythingLLM 使其易于使用。",
|
||||
"AnythingLLM 可以完全在你的本地计算机上运行,几乎没有开销,你甚至不会注意到它的存在!无需 GPU。也可以进行云端和本地安装。\nAI 工具生态系统每天都在变得更强大。AnythingLLM 使其易于使用。",
|
||||
githubIssue: "在 Github 上创建问题",
|
||||
user1: "我该如何开始?!",
|
||||
part4:
|
||||
"很简单。所有集合都组织成我们称之为“工作区”的桶。工作区是文件、文档、图像、PDF 和其他文件的存储桶,这些文件将被转换为 LLM 可以理解和在对话中使用的内容。\n\n您可以随时添加和删除文件。",
|
||||
createWorkspace: "创建您的第一个工作区",
|
||||
"很简单。所有集合都组织成我们称之为“工作区”的桶。工作区是文件、文档、图像、PDF 和其他文件的存储桶,这些文件将被转换为 LLM 可以理解和在对话中使用的内容。\n\n你可以随时添加和删除文件。",
|
||||
createWorkspace: "创建你的第一个工作区",
|
||||
user2: "这像是一个 AI Dropbox 吗?那么聊天呢?它是一个聊天机器人,不是吗?",
|
||||
part5:
|
||||
"AnythingLLM 不仅仅是一个更智能的 Dropbox。\n\nAnythingLLM 提供了两种与您的数据交流的方式:\n\n<i>查询:</i> 您的聊天将返回在您的工作区中访问的文档中找到的数据或推论。向工作区添加更多文档会使其更智能!\n\n<i>对话:</i> 您的文档和正在进行的聊天记录同时为 LLM 知识做出贡献。非常适合添加基于文本的实时信息或纠正 LLM 可能存在的误解。\n\n您可以在聊天过程中 <i>切换模式!</i>",
|
||||
"AnythingLLM 不仅仅是一个更智能的 Dropbox。\n\nAnythingLLM 提供了两种与你的数据交流的方式:\n\n<i>查询:</i> 你的聊天将返回在你的工作区中访问的文档中找到的数据或推论。向工作区添加更多文档会使其更智能!\n\n<i>对话:</i> 你的文档和正在进行的聊天记录同时为 LLM 知识做出贡献。非常适合添加基于文本的实时信息或纠正 LLM 可能存在的误解。\n\n你可以在聊天过程中 <i>切换模式!</i>",
|
||||
user3: "哇,这听起来很棒,让我马上试试!",
|
||||
part6: "玩得开心!",
|
||||
starOnGithub: "在 GitHub 上加星",
|
||||
contact: "联系 Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "新工作区",
|
||||
placeholder: "我的工作区",
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "通用设置",
|
||||
chat: "聊天设置",
|
||||
|
@ -100,8 +147,6 @@ const TRANSLATIONS = {
|
|||
members: "成员",
|
||||
agent: "代理配置",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "向量数量",
|
||||
|
@ -112,7 +157,7 @@ const TRANSLATIONS = {
|
|||
},
|
||||
message: {
|
||||
title: "建议的聊天消息",
|
||||
description: "自定义将向您的工作区用户建议的消息。",
|
||||
description: "自定义将向你的工作区用户建议的消息。",
|
||||
add: "添加新消息",
|
||||
save: "保存消息",
|
||||
heading: "向我解释",
|
||||
|
@ -129,13 +174,11 @@ const TRANSLATIONS = {
|
|||
description: "删除此工作区及其所有数据。这将删除所有用户的工作区。",
|
||||
delete: "删除工作区",
|
||||
deleting: "正在删除工作区...",
|
||||
"confirm-start": "您即将删除整个",
|
||||
"confirm-start": "你即将删除整个",
|
||||
"confirm-end":
|
||||
"工作区。这将删除矢量数据库中的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "工作区 LLM 提供者",
|
||||
|
@ -174,24 +217,22 @@ const TRANSLATIONS = {
|
|||
prompt: {
|
||||
title: "聊天提示",
|
||||
description:
|
||||
"将在此工作区上使用的提示。定义 AI 生成响应的上下文和指令。您应该提供精心设计的提示,以便人工智能可以生成相关且准确的响应。",
|
||||
"将在此工作区上使用的提示。定义 AI 生成响应的上下文和指令。你应该提供精心设计的提示,以便人工智能可以生成相关且准确的响应。",
|
||||
},
|
||||
refusal: {
|
||||
title: "查询模式拒绝响应",
|
||||
"desc-start": "当处于",
|
||||
query: "查询",
|
||||
"desc-end": "模式时,当未找到上下文时,您可能希望返回自定义拒绝响应。",
|
||||
"desc-end": "模式时,当未找到上下文时,你可能希望返回自定义拒绝响应。",
|
||||
},
|
||||
temperature: {
|
||||
title: "LLM 温度",
|
||||
"desc-start": "此设置控制您的 LLM 回答的“创意”程度",
|
||||
"desc-start": "此设置控制你的 LLM 回答的“创意”程度",
|
||||
"desc-end":
|
||||
"数字越高越有创意。对于某些模型,如果设置得太高,可能会导致响应不一致。",
|
||||
hint: "大多数 LLM 都有各种可接受的有效值范围。请咨询您的LLM提供商以获取该信息。",
|
||||
hint: "大多数 LLM 都有各种可接受的有效值范围。请咨询你的LLM提供商以获取该信息。",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database Settings
|
||||
"vector-workspace": {
|
||||
identifier: "向量数据库标识符",
|
||||
snippets: {
|
||||
|
@ -213,13 +254,11 @@ const TRANSLATIONS = {
|
|||
reset: "重置向量数据库",
|
||||
resetting: "清除向量...",
|
||||
confirm:
|
||||
"您将重置此工作区的矢量数据库。这将删除当前嵌入的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。",
|
||||
"你将重置此工作区的矢量数据库。这将删除当前嵌入的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。",
|
||||
success: "向量数据库已重置。",
|
||||
error: "无法重置工作区向量数据库!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"不明确支持工具调用的 LLMs 的性能高度依赖于模型的功能和准确性。有些能力可能受到限制或不起作用。",
|
||||
|
@ -243,7 +282,7 @@ const TRANSLATIONS = {
|
|||
rag: {
|
||||
title: "RAG 和长期记忆",
|
||||
description:
|
||||
'允许代理利用您的本地文档来回答查询,或要求代理"记住"长期记忆检索的内容片段。',
|
||||
'允许代理利用你的本地文档来回答查询,或要求代理"记住"长期记忆检索的内容片段。',
|
||||
},
|
||||
view: {
|
||||
title: "查看和总结文档",
|
||||
|
@ -260,18 +299,16 @@ const TRANSLATIONS = {
|
|||
save: {
|
||||
title: "生成并保存文件到浏览器",
|
||||
description:
|
||||
"使默认代理能够生成并写入文件,这些文件可以保存并在您的浏览器中下载。",
|
||||
"使默认代理能够生成并写入文件,这些文件可以保存并在你的浏览器中下载。",
|
||||
},
|
||||
web: {
|
||||
title: "实时网络搜索和浏览",
|
||||
"desc-start":
|
||||
"通过连接到网络搜索(SERP)提供者,使您的代理能够搜索网络以回答您的问题。",
|
||||
"通过连接到网络搜索(SERP)提供者,使你的代理能够搜索网络以回答你的问题。",
|
||||
"desc-end": "在代理会话期间,网络搜索将不起作用,直到此设置完成。",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chat
|
||||
recorded: {
|
||||
title: "工作区聊天历史记录",
|
||||
description: "这些是用户发送的所有聊天记录和消息,按创建日期排序。",
|
||||
|
@ -285,13 +322,12 @@ const TRANSLATIONS = {
|
|||
at: "发送时间",
|
||||
},
|
||||
},
|
||||
|
||||
appearance: {
|
||||
title: "外观",
|
||||
description: "自定义平台的外观设置。",
|
||||
logo: {
|
||||
title: "自定义图标",
|
||||
description: "上传您的自定义图标,让您的聊天机器人成为您的。",
|
||||
description: "上传你的自定义图标,让你的聊天机器人成为你的。",
|
||||
add: "添加自定义图标",
|
||||
recommended: "建议尺寸:800 x 200",
|
||||
remove: "移除",
|
||||
|
@ -315,8 +351,6 @@ const TRANSLATIONS = {
|
|||
link: "链接",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API 密钥",
|
||||
description: "API 密钥允许持有者以编程方式访问和管理此 AnythingLLM 实例。",
|
||||
|
@ -328,42 +362,37 @@ const TRANSLATIONS = {
|
|||
created: "创建",
|
||||
},
|
||||
},
|
||||
|
||||
// LLM Preferences
|
||||
llm: {
|
||||
title: "LLM 首选项",
|
||||
description:
|
||||
"这些是您首选的 LLM 聊天和嵌入提供商的凭据和设置。重要的是,这些密钥是最新的和正确的,否则 AnythingLLM 将无法正常运行。",
|
||||
"这些是你首选的 LLM 聊天和嵌入提供商的凭据和设置。重要的是,这些密钥是最新的和正确的,否则 AnythingLLM 将无法正常运行。",
|
||||
provider: "LLM 提供商",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "转录模型首选项",
|
||||
description:
|
||||
"这些是您的首选转录模型提供商的凭据和设置。重要的是这些密钥是最新且正确的,否则媒体文件和音频将无法转录。",
|
||||
"这些是你的首选转录模型提供商的凭据和设置。重要的是这些密钥是最新且正确的,否则媒体文件和音频将无法转录。",
|
||||
provider: "转录提供商",
|
||||
"warn-start":
|
||||
"在 RAM 或 CPU 有限的计算机上使用本地耳语模型可能会在处理媒体文件时停止 AnythingLLM。",
|
||||
"warn-recommend": "我们建议至少 2GB RAM 并上传 <10Mb 的文件。",
|
||||
"warn-end": "内置模型将在首次使用时自动下载。",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "嵌入首选项",
|
||||
"desc-start":
|
||||
"当使用本身不支持嵌入引擎的 LLM 时,您可能需要额外指定用于嵌入文本的凭据。",
|
||||
"当使用本身不支持嵌入引擎的 LLM 时,你可能需要额外指定用于嵌入文本的凭据。",
|
||||
"desc-end":
|
||||
"嵌入是将文本转换为矢量的过程。需要这些凭据才能将您的文件和提示转换为 AnythingLLM 可以用来处理的格式。",
|
||||
"嵌入是将文本转换为矢量的过程。需要这些凭据才能将你的文件和提示转换为 AnythingLLM 可以用来处理的格式。",
|
||||
provider: {
|
||||
title: "嵌入引擎提供商",
|
||||
description: "使用 AnythingLLM 的本机嵌入引擎时不需要设置。",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "文本拆分和分块首选项",
|
||||
"desc-start":
|
||||
"有时,您可能希望更改新文档在插入到矢量数据库之前拆分和分块的默认方式。",
|
||||
"有时,你可能希望更改新文档在插入到矢量数据库之前拆分和分块的默认方式。",
|
||||
"desc-end": "只有在了解文本拆分的工作原理及其副作用时,才应修改此设置。",
|
||||
"warn-start": "此处的更改仅适用于",
|
||||
"warn-center": "新嵌入的文档",
|
||||
|
@ -378,8 +407,6 @@ const TRANSLATIONS = {
|
|||
description: "这是在两个相邻文本块之间分块期间发生的最大字符重叠。",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "向量数据库",
|
||||
description:
|
||||
|
@ -389,12 +416,10 @@ const TRANSLATIONS = {
|
|||
description: "LanceDB 不需要任何配置。",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chats
|
||||
embeddable: {
|
||||
title: "可嵌入的聊天小部件",
|
||||
description:
|
||||
"可嵌入的聊天小部件是与单个工作区绑定的面向公众的聊天界面。这些允许您构建工作区,然后您可以将其发布到全世界。",
|
||||
"可嵌入的聊天小部件是与单个工作区绑定的面向公众的聊天界面。这些允许你构建工作区,然后你可以将其发布到全世界。",
|
||||
create: "创建嵌入式对话",
|
||||
table: {
|
||||
workspace: "工作区",
|
||||
|
@ -402,12 +427,10 @@ const TRANSLATIONS = {
|
|||
Active: "活动域",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat History
|
||||
"embed-chats": {
|
||||
title: "嵌入的聊天历史纪录",
|
||||
export: "导出",
|
||||
description: "这些是您发布的任何嵌入的所有记录的聊天和消息。",
|
||||
description: "这些是你发布的任何嵌入的所有记录的聊天和消息。",
|
||||
table: {
|
||||
embed: "嵌入",
|
||||
sender: "发送者",
|
||||
|
@ -416,32 +439,29 @@ const TRANSLATIONS = {
|
|||
at: "发送时间",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "多用户模式",
|
||||
description: "通过激活多用户模式来设置您的实例以支持您的团队。",
|
||||
description: "通过激活多用户模式来设置你的实例以支持你的团队。",
|
||||
enable: {
|
||||
"is-enable": "多用户模式已启用",
|
||||
enable: "启用多用户模式",
|
||||
description:
|
||||
"默认情况下,您将是唯一的管理员。作为管理员,您需要为所有新用户或管理员创建账户。不要丢失您的密码,因为只有管理员用户可以重置密码。",
|
||||
"默认情况下,你将是唯一的管理员。作为管理员,你需要为所有新用户或管理员创建账户。不要丢失你的密码,因为只有管理员用户可以重置密码。",
|
||||
username: "管理员账户用户名",
|
||||
password: "管理员账户密码",
|
||||
},
|
||||
password: {
|
||||
title: "密码保护",
|
||||
description:
|
||||
"用密码保护您的AnythingLLM实例。如果您忘记了密码,那么没有恢复方法,所以请确保保存这个密码。",
|
||||
"用密码保护你的AnythingLLM实例。如果你忘记了密码,那么没有恢复方法,所以请确保保存这个密码。",
|
||||
},
|
||||
instance: {
|
||||
title: "实例密码保护",
|
||||
description:
|
||||
"默认情况下,您将是唯一的管理员。作为管理员,您需要为所有新用户或管理员创建账户。不要丢失您的密码,因为只有管理员用户可以重置密码。",
|
||||
"默认情况下,你将是唯一的管理员。作为管理员,你需要为所有新用户或管理员创建账户。不要丢失你的密码,因为只有管理员用户可以重置密码。",
|
||||
password: "实例密码",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "事件日志",
|
||||
description: "查看此实例上发生的所有操作和事件以进行监控。",
|
||||
|
@ -452,12 +472,10 @@ const TRANSLATIONS = {
|
|||
occurred: "发生时间",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "隐私和数据处理",
|
||||
description:
|
||||
"这是您对如何处理连接的第三方提供商和AnythingLLM的数据的配置。",
|
||||
"这是你对如何处理连接的第三方提供商和AnythingLLM的数据的配置。",
|
||||
llm: "LLM 选择",
|
||||
embedding: "嵌入首选项",
|
||||
vector: "向量数据库",
|
||||
|
|
|
@ -1,4 +1,57 @@
|
|||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
onboarding: {
|
||||
home: {
|
||||
title: "歡迎使用",
|
||||
getStarted: "開始使用",
|
||||
},
|
||||
llm: {
|
||||
title: "LLM 偏好",
|
||||
description:
|
||||
"AnythingLLM 可以與多家 LLM 提供商合作。這將是處理聊天的服務。",
|
||||
},
|
||||
userSetup: {
|
||||
title: "使用者設定",
|
||||
description: "配置您的使用者設定。",
|
||||
howManyUsers: "將有多少使用者使用此實例?",
|
||||
justMe: "只有我",
|
||||
myTeam: "我的團隊",
|
||||
instancePassword: "實例密碼",
|
||||
setPassword: "您想要設定密碼嗎?",
|
||||
passwordReq: "密碼必須至少包含 8 個字元。",
|
||||
passwordWarn: "保存此密碼很重要,因為沒有恢復方法。",
|
||||
adminUsername: "管理員帳號使用者名稱",
|
||||
adminUsernameReq:
|
||||
"使用者名稱必須至少為 6 個字元,並且只能包含小寫字母、數字、底線和連字號,不含空格。",
|
||||
adminPassword: "管理員帳號密碼",
|
||||
adminPasswordReq: "密碼必須至少包含 8 個字元。",
|
||||
teamHint:
|
||||
"預設情況下,您將是唯一的管理員。完成入職後,您可以創建和邀請其他人成為使用者或管理員。不要遺失您的密碼,因為只有管理員可以重置密碼。",
|
||||
},
|
||||
data: {
|
||||
title: "資料處理與隱私",
|
||||
description: "我們致力於在涉及您的個人資料時提供透明和控制。",
|
||||
settingsHint: "這些設定可以隨時在設定中重新配置。",
|
||||
},
|
||||
survey: {
|
||||
title: "歡迎使用 AnythingLLM",
|
||||
description: "幫助我們為您的需求打造 AnythingLLM。可選。",
|
||||
email: "您的電子郵件是什麼?",
|
||||
useCase: "您將如何使用 AnythingLLM?",
|
||||
useCaseWork: "用於工作",
|
||||
useCasePersonal: "用於個人使用",
|
||||
useCaseOther: "其他",
|
||||
comment: "您是如何聽說 AnythingLLM 的?",
|
||||
commentPlaceholder:
|
||||
"Reddit,Twitter,GitHub,YouTube 等 - 讓我們知道您是如何找到我們的!",
|
||||
skip: "跳過調查",
|
||||
thankYou: "感謝您的反饋!",
|
||||
},
|
||||
workspace: {
|
||||
title: "創建您的第一個工作區",
|
||||
description: "創建您的第一個工作區並開始使用 AnythingLLM。",
|
||||
},
|
||||
},
|
||||
common: {
|
||||
"workspaces-name": "工作區名稱",
|
||||
error: "錯誤",
|
||||
|
@ -9,9 +62,10 @@ const TRANSLATIONS = {
|
|||
save: "儲存修改",
|
||||
previous: "上一頁",
|
||||
next: "下一頁",
|
||||
optional: null,
|
||||
yes: null,
|
||||
no: null,
|
||||
},
|
||||
|
||||
// 設定側邊欄選單項目
|
||||
settings: {
|
||||
title: "系統設定",
|
||||
system: "一般設定",
|
||||
|
@ -40,8 +94,6 @@ const TRANSLATIONS = {
|
|||
contact: "聯絡支援",
|
||||
"browser-extension": "瀏覽器擴充功能",
|
||||
},
|
||||
|
||||
// 頁面定義
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "歡迎使用",
|
||||
|
@ -64,7 +116,6 @@ const TRANSLATIONS = {
|
|||
"back-to-login": "返回登入頁面",
|
||||
},
|
||||
},
|
||||
|
||||
welcomeMessage: {
|
||||
part1:
|
||||
"歡迎使用 AnythingLLM,AnythingLLM 是由 Mintplex Labs 開發的開源 AI 工具,它能將任何內容轉換成可供查詢和對話的訓練模型對話機器人。AnythingLLM 採用 BYOK(自備金鑰)軟體模式,除了您想使用的服務之外,本軟體不收取任何訂閱費、費用或其他費用。",
|
||||
|
@ -85,13 +136,10 @@ const TRANSLATIONS = {
|
|||
starOnGithub: "在 GitHub 上給我們星星",
|
||||
contact: "聯絡 Mintplex Labs",
|
||||
},
|
||||
|
||||
"new-workspace": {
|
||||
title: "新增工作區",
|
||||
placeholder: "我的工作區",
|
||||
},
|
||||
|
||||
// 工作區設定選單項目
|
||||
"workspaces—settings": {
|
||||
general: "一般設定",
|
||||
chat: "對話設定",
|
||||
|
@ -99,8 +147,6 @@ const TRANSLATIONS = {
|
|||
members: "成員管理",
|
||||
agent: "智慧代理人設定",
|
||||
},
|
||||
|
||||
// 一般外觀
|
||||
general: {
|
||||
vector: {
|
||||
title: "向量計數",
|
||||
|
@ -133,8 +179,6 @@ const TRANSLATIONS = {
|
|||
"工作區。這將會移除向量資料庫中的所有向量嵌入。\n\n原始檔案將保持不變。此動作無法復原。",
|
||||
},
|
||||
},
|
||||
|
||||
// 對話設定
|
||||
chat: {
|
||||
llm: {
|
||||
title: "工作區 LLM 提供者",
|
||||
|
@ -188,8 +232,6 @@ const TRANSLATIONS = {
|
|||
hint: "大多數 LLM 都有各種可接受的有效值範圍。請查詢您的 LLM 提供者以取得該資訊。",
|
||||
},
|
||||
},
|
||||
|
||||
// 向量資料庫
|
||||
"vector-workspace": {
|
||||
identifier: "向量資料庫識別碼",
|
||||
snippets: {
|
||||
|
@ -216,8 +258,6 @@ const TRANSLATIONS = {
|
|||
success: "工作區向量資料庫已重設!",
|
||||
},
|
||||
},
|
||||
|
||||
// 智慧代理人設定
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"不直接支援工具呼叫的 LLM 的效能,高度取決於模型的功能和精確度。某些功能可能受限或無法使用。",
|
||||
|
@ -234,7 +274,6 @@ const TRANSLATIONS = {
|
|||
description: "此工作區 @agent 智慧代理人將使用的特定 LLM 模型。",
|
||||
wait: "-- 等待模型中 --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "預設智慧代理人技能",
|
||||
description:
|
||||
|
@ -271,8 +310,6 @@ const TRANSLATIONS = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// 工作區對話紀錄
|
||||
recorded: {
|
||||
title: "工作區對話紀錄",
|
||||
description: "這些是所有已記錄的對話和訊息,依建立日期排序。",
|
||||
|
@ -286,8 +323,6 @@ const TRANSLATIONS = {
|
|||
at: "傳送時間",
|
||||
},
|
||||
},
|
||||
|
||||
// 外觀
|
||||
appearance: {
|
||||
title: "外觀",
|
||||
description: "自訂平台的外觀設定。",
|
||||
|
@ -317,8 +352,6 @@ const TRANSLATIONS = {
|
|||
link: "連結",
|
||||
},
|
||||
},
|
||||
|
||||
// API 金鑰
|
||||
api: {
|
||||
title: "API 金鑰",
|
||||
description:
|
||||
|
@ -331,14 +364,12 @@ const TRANSLATIONS = {
|
|||
created: "建立時間",
|
||||
},
|
||||
},
|
||||
|
||||
llm: {
|
||||
title: "LLM 偏好設定",
|
||||
description:
|
||||
"這些是您偏好的 LLM 對話與嵌入提供者的憑證和設定。確保這些金鑰是最新且正確的,否則 AnythingLLM 將無法正常運作。",
|
||||
provider: "LLM 提供者",
|
||||
},
|
||||
|
||||
transcription: {
|
||||
title: "語音轉錄模型偏好設定",
|
||||
description:
|
||||
|
@ -349,7 +380,6 @@ const TRANSLATIONS = {
|
|||
"warn-recommend": "我們建議至少 2GB 的記憶體,並且上傳小於 10MB 的檔案。",
|
||||
"warn-end": "內建模型將會在第一次使用時自動下載。",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "向量嵌入偏好設定",
|
||||
"desc-start":
|
||||
|
@ -361,7 +391,6 @@ const TRANSLATIONS = {
|
|||
description: "使用 AnythingLLM 的原生嵌入引擎時,不需要任何設定。",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "文字分割與區塊化偏好設定",
|
||||
"desc-start":
|
||||
|
@ -376,14 +405,11 @@ const TRANSLATIONS = {
|
|||
description: "這是單一向量中可包含的最大字元長度。",
|
||||
recommend: "嵌入模型的最大長度為",
|
||||
},
|
||||
|
||||
overlap: {
|
||||
title: "文字區塊重疊",
|
||||
description: "這是區塊化過程中,兩個相鄰文字區塊之間的最大字元重疊數。",
|
||||
},
|
||||
},
|
||||
|
||||
// 向量資料庫
|
||||
vector: {
|
||||
title: "向量資料庫",
|
||||
description:
|
||||
|
@ -393,8 +419,6 @@ const TRANSLATIONS = {
|
|||
description: "使用 LanceDB 不需要任何設定。",
|
||||
},
|
||||
},
|
||||
|
||||
// 可嵌入對話小工具
|
||||
embeddable: {
|
||||
title: "可嵌入對話小工具",
|
||||
description:
|
||||
|
@ -406,7 +430,6 @@ const TRANSLATIONS = {
|
|||
Active: "已啟用網域",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "嵌入對話",
|
||||
export: "匯出",
|
||||
|
@ -419,7 +442,6 @@ const TRANSLATIONS = {
|
|||
at: "傳送時間",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "多使用者模式",
|
||||
description: "透過啟用多使用者模式來設定您的系統,以支援您的團隊。",
|
||||
|
@ -443,8 +465,6 @@ const TRANSLATIONS = {
|
|||
password: "系統密碼",
|
||||
},
|
||||
},
|
||||
|
||||
// 事件記錄
|
||||
event: {
|
||||
title: "事件記錄",
|
||||
description: "檢視此系統上發生的所有動作和事件,以進行監控。",
|
||||
|
@ -455,8 +475,6 @@ const TRANSLATIONS = {
|
|||
occurred: "發生時間",
|
||||
},
|
||||
},
|
||||
|
||||
// 隱私與資料處理
|
||||
privacy: {
|
||||
title: "隱私與資料處理",
|
||||
description:
|
||||
|
|
BIN
frontend/src/media/logo/anything-llm-infinity.png
Normal file
BIN
frontend/src/media/logo/anything-llm-infinity.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 552 B |
149
frontend/src/models/agentFlows.js
Normal file
149
frontend/src/models/agentFlows.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
import { API_BASE } from "@/utils/constants";
|
||||
import { baseHeaders } from "@/utils/request";
|
||||
|
||||
const AgentFlows = {
|
||||
/**
|
||||
* Save a flow configuration
|
||||
* @param {string} name - Display name of the flow
|
||||
* @param {object} config - The configuration object for the flow
|
||||
* @param {string} [uuid] - Optional UUID for updating existing flow
|
||||
* @returns {Promise<{success: boolean, error: string | null, flow: {name: string, config: object, uuid: string} | null}>}
|
||||
*/
|
||||
saveFlow: async (name, config, uuid = null) => {
|
||||
return await fetch(`${API_BASE}/agent-flows/save`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...baseHeaders(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ name, config, uuid }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(response.error || "Failed to save flow");
|
||||
return res;
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => ({
|
||||
success: false,
|
||||
error: e.message,
|
||||
flow: null,
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* List all available flows in the system
|
||||
* @returns {Promise<{success: boolean, error: string | null, flows: Array<{name: string, uuid: string, description: string, steps: Array}>}>}
|
||||
*/
|
||||
listFlows: async () => {
|
||||
return await fetch(`${API_BASE}/agent-flows/list`, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => ({
|
||||
success: false,
|
||||
error: e.message,
|
||||
flows: [],
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a specific flow by UUID
|
||||
* @param {string} uuid - The UUID of the flow to retrieve
|
||||
* @returns {Promise<{success: boolean, error: string | null, flow: {name: string, config: object, uuid: string} | null}>}
|
||||
*/
|
||||
getFlow: async (uuid) => {
|
||||
return await fetch(`${API_BASE}/agent-flows/${uuid}`, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(response.error || "Failed to get flow");
|
||||
return res;
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => ({
|
||||
success: false,
|
||||
error: e.message,
|
||||
flow: null,
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a specific flow
|
||||
* @param {string} uuid - The UUID of the flow to run
|
||||
* @param {object} variables - Optional variables to pass to the flow
|
||||
* @returns {Promise<{success: boolean, error: string | null, results: object | null}>}
|
||||
*/
|
||||
// runFlow: async (uuid, variables = {}) => {
|
||||
// return await fetch(`${API_BASE}/agent-flows/${uuid}/run`, {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// ...baseHeaders(),
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: JSON.stringify({ variables }),
|
||||
// })
|
||||
// .then((res) => {
|
||||
// if (!res.ok) throw new Error(response.error || "Failed to run flow");
|
||||
// return res;
|
||||
// })
|
||||
// .then((res) => res.json())
|
||||
// .catch((e) => ({
|
||||
// success: false,
|
||||
// error: e.message,
|
||||
// results: null,
|
||||
// }));
|
||||
// },
|
||||
|
||||
/**
|
||||
* Delete a specific flow
|
||||
* @param {string} uuid - The UUID of the flow to delete
|
||||
* @returns {Promise<{success: boolean, error: string | null}>}
|
||||
*/
|
||||
deleteFlow: async (uuid) => {
|
||||
return await fetch(`${API_BASE}/agent-flows/${uuid}`, {
|
||||
method: "DELETE",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(response.error || "Failed to delete flow");
|
||||
return res;
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => ({
|
||||
success: false,
|
||||
error: e.message,
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle a flow's active status
|
||||
* @param {string} uuid - The UUID of the flow to toggle
|
||||
* @param {boolean} active - The new active status
|
||||
* @returns {Promise<{success: boolean, error: string | null}>}
|
||||
*/
|
||||
toggleFlow: async (uuid, active) => {
|
||||
try {
|
||||
const result = await fetch(`${API_BASE}/agent-flows/${uuid}/toggle`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...baseHeaders(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ active }),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(res.error || "Failed to toggle flow");
|
||||
return res;
|
||||
})
|
||||
.then((res) => res.json());
|
||||
return { success: true, flow: result.flow };
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle flow:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default AgentFlows;
|
|
@ -136,6 +136,7 @@ const DataConnector = {
|
|||
spaceKey,
|
||||
username,
|
||||
accessToken,
|
||||
personalAccessToken,
|
||||
cloud,
|
||||
}) {
|
||||
return await fetch(`${API_BASE}/ext/confluence`, {
|
||||
|
@ -146,6 +147,7 @@ const DataConnector = {
|
|||
spaceKey,
|
||||
username,
|
||||
accessToken,
|
||||
personalAccessToken,
|
||||
cloud,
|
||||
}),
|
||||
})
|
||||
|
|
68
frontend/src/pages/Admin/AgentBuilder/AddBlockMenu/index.jsx
Normal file
68
frontend/src/pages/Admin/AgentBuilder/AddBlockMenu/index.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import React, { useRef, useEffect } from "react";
|
||||
import { Plus, CaretDown } from "@phosphor-icons/react";
|
||||
import { BLOCK_TYPES, BLOCK_INFO } from "../BlockList";
|
||||
|
||||
export default function AddBlockMenu({
|
||||
showBlockMenu,
|
||||
setShowBlockMenu,
|
||||
addBlock,
|
||||
}) {
|
||||
const menuRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
setShowBlockMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [setShowBlockMenu]);
|
||||
|
||||
return (
|
||||
<div className="relative mt-4 w-[280px] mx-auto pb-[50%]" ref={menuRef}>
|
||||
<button
|
||||
onClick={() => setShowBlockMenu(!showBlockMenu)}
|
||||
className="transition-all duration-300 w-full p-2.5 bg-theme-action-menu-bg hover:bg-theme-action-menu-item-hover border border-white/10 rounded-lg text-white flex items-center justify-center gap-2 text-sm font-medium"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Block
|
||||
<CaretDown
|
||||
className={`w-3.5 h-3.5 transition-transform duration-300 ${showBlockMenu ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
{showBlockMenu && (
|
||||
<div className="absolute left-0 right-0 mt-2 bg-theme-action-menu-bg border border-white/10 rounded-lg shadow-lg overflow-hidden z-10 animate-fadeUpIn">
|
||||
{Object.entries(BLOCK_INFO).map(
|
||||
([type, info]) =>
|
||||
type !== BLOCK_TYPES.START &&
|
||||
type !== BLOCK_TYPES.FINISH &&
|
||||
type !== BLOCK_TYPES.FLOW_INFO && (
|
||||
<button
|
||||
key={type}
|
||||
onClick={() => {
|
||||
addBlock(type);
|
||||
setShowBlockMenu(false);
|
||||
}}
|
||||
className="w-full p-2.5 flex items-center gap-3 hover:bg-theme-action-menu-item-hover text-white transition-colors duration-300 group"
|
||||
>
|
||||
<div className="w-7 h-7 rounded-lg bg-white/10 flex items-center justify-center">
|
||||
<div className="w-fit h-fit text-white">{info.icon}</div>
|
||||
</div>
|
||||
<div className="text-left flex-1">
|
||||
<div className="text-sm font-medium">{info.label}</div>
|
||||
<div className="text-xs text-white/60">
|
||||
{info.description}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
305
frontend/src/pages/Admin/AgentBuilder/BlockList/index.jsx
Normal file
305
frontend/src/pages/Admin/AgentBuilder/BlockList/index.jsx
Normal file
|
@ -0,0 +1,305 @@
|
|||
import React from "react";
|
||||
import {
|
||||
X,
|
||||
CaretUp,
|
||||
CaretDown,
|
||||
Globe,
|
||||
Browser,
|
||||
Brain,
|
||||
Flag,
|
||||
Info,
|
||||
BracketsCurly,
|
||||
} from "@phosphor-icons/react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import StartNode from "../nodes/StartNode";
|
||||
import ApiCallNode from "../nodes/ApiCallNode";
|
||||
import WebsiteNode from "../nodes/WebsiteNode";
|
||||
import FileNode from "../nodes/FileNode";
|
||||
import CodeNode from "../nodes/CodeNode";
|
||||
import LLMInstructionNode from "../nodes/LLMInstructionNode";
|
||||
import FinishNode from "../nodes/FinishNode";
|
||||
import WebScrapingNode from "../nodes/WebScrapingNode";
|
||||
import FlowInfoNode from "../nodes/FlowInfoNode";
|
||||
|
||||
const BLOCK_TYPES = {
|
||||
FLOW_INFO: "flowInfo",
|
||||
START: "start",
|
||||
API_CALL: "apiCall",
|
||||
// WEBSITE: "website", // Temporarily disabled
|
||||
// FILE: "file", // Temporarily disabled
|
||||
// CODE: "code", // Temporarily disabled
|
||||
LLM_INSTRUCTION: "llmInstruction",
|
||||
WEB_SCRAPING: "webScraping",
|
||||
FINISH: "finish",
|
||||
};
|
||||
|
||||
const BLOCK_INFO = {
|
||||
[BLOCK_TYPES.FLOW_INFO]: {
|
||||
label: "Flow Infomation",
|
||||
icon: <Info className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Basic flow information",
|
||||
defaultConfig: {
|
||||
name: "",
|
||||
description: "",
|
||||
},
|
||||
getSummary: (config) => config.name || "Untitled Flow",
|
||||
},
|
||||
[BLOCK_TYPES.START]: {
|
||||
label: "Flow Variables",
|
||||
icon: <BracketsCurly className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Configure agent variables and settings",
|
||||
getSummary: (config) => {
|
||||
const varCount = config.variables?.filter((v) => v.name)?.length || 0;
|
||||
return `${varCount} variable${varCount !== 1 ? "s" : ""} defined`;
|
||||
},
|
||||
},
|
||||
[BLOCK_TYPES.API_CALL]: {
|
||||
label: "API Call",
|
||||
icon: <Globe className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Make an HTTP request",
|
||||
defaultConfig: {
|
||||
url: "",
|
||||
method: "GET",
|
||||
headers: [],
|
||||
bodyType: "json",
|
||||
body: "",
|
||||
formData: [],
|
||||
responseVariable: "",
|
||||
},
|
||||
getSummary: (config) =>
|
||||
`${config.method || "GET"} ${config.url || "(no URL)"}`,
|
||||
},
|
||||
// TODO: Implement website, file, and code blocks
|
||||
/* [BLOCK_TYPES.WEBSITE]: {
|
||||
label: "Open Website",
|
||||
icon: <Browser className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Navigate to a URL",
|
||||
defaultConfig: {
|
||||
url: "",
|
||||
selector: "",
|
||||
action: "read",
|
||||
value: "",
|
||||
resultVariable: "",
|
||||
},
|
||||
getSummary: (config) =>
|
||||
`${config.action || "read"} from ${config.url || "(no URL)"}`,
|
||||
},
|
||||
[BLOCK_TYPES.FILE]: {
|
||||
label: "Open File",
|
||||
icon: <File className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Read or write to a file",
|
||||
defaultConfig: {
|
||||
path: "",
|
||||
operation: "read",
|
||||
content: "",
|
||||
resultVariable: "",
|
||||
},
|
||||
getSummary: (config) =>
|
||||
`${config.operation || "read"} ${config.path || "(no path)"}`,
|
||||
},
|
||||
[BLOCK_TYPES.CODE]: {
|
||||
label: "Code Execution",
|
||||
icon: <Code className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Execute code snippets",
|
||||
defaultConfig: {
|
||||
language: "javascript",
|
||||
code: "",
|
||||
resultVariable: "",
|
||||
},
|
||||
getSummary: (config) => `Run ${config.language || "javascript"} code`,
|
||||
},
|
||||
*/
|
||||
[BLOCK_TYPES.LLM_INSTRUCTION]: {
|
||||
label: "LLM Instruction",
|
||||
icon: <Brain className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Process data using LLM instructions",
|
||||
defaultConfig: {
|
||||
instruction: "",
|
||||
inputVariable: "",
|
||||
resultVariable: "",
|
||||
},
|
||||
getSummary: (config) => config.instruction || "No instruction",
|
||||
},
|
||||
[BLOCK_TYPES.WEB_SCRAPING]: {
|
||||
label: "Web Scraping",
|
||||
icon: <Browser className="w-5 h-5 text-theme-text-primary" />,
|
||||
description: "Scrape content from a webpage",
|
||||
defaultConfig: {
|
||||
url: "",
|
||||
resultVariable: "",
|
||||
},
|
||||
getSummary: (config) => config.url || "No URL specified",
|
||||
},
|
||||
[BLOCK_TYPES.FINISH]: {
|
||||
label: "Flow Complete",
|
||||
icon: <Flag className="w-4 h-4" />,
|
||||
description: "End of agent flow",
|
||||
getSummary: () => "Flow will end here",
|
||||
defaultConfig: {},
|
||||
renderConfig: () => null,
|
||||
},
|
||||
};
|
||||
|
||||
export default function BlockList({
|
||||
blocks,
|
||||
updateBlockConfig,
|
||||
removeBlock,
|
||||
toggleBlockExpansion,
|
||||
renderVariableSelect,
|
||||
onDeleteVariable,
|
||||
moveBlock,
|
||||
refs,
|
||||
}) {
|
||||
const renderBlockConfig = (block) => {
|
||||
const props = {
|
||||
config: block.config,
|
||||
onConfigChange: (config) => updateBlockConfig(block.id, config),
|
||||
renderVariableSelect,
|
||||
onDeleteVariable,
|
||||
};
|
||||
|
||||
switch (block.type) {
|
||||
case BLOCK_TYPES.FLOW_INFO:
|
||||
return <FlowInfoNode {...props} ref={refs} />;
|
||||
case BLOCK_TYPES.START:
|
||||
return <StartNode {...props} />;
|
||||
case BLOCK_TYPES.API_CALL:
|
||||
return <ApiCallNode {...props} />;
|
||||
case BLOCK_TYPES.WEBSITE:
|
||||
return <WebsiteNode {...props} />;
|
||||
case BLOCK_TYPES.FILE:
|
||||
return <FileNode {...props} />;
|
||||
case BLOCK_TYPES.CODE:
|
||||
return <CodeNode {...props} />;
|
||||
case BLOCK_TYPES.LLM_INSTRUCTION:
|
||||
return <LLMInstructionNode {...props} />;
|
||||
case BLOCK_TYPES.WEB_SCRAPING:
|
||||
return <WebScrapingNode {...props} />;
|
||||
case BLOCK_TYPES.FINISH:
|
||||
return <FinishNode />;
|
||||
default:
|
||||
return <div>Configuration options coming soon...</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{blocks.map((block, index) => (
|
||||
<div key={block.id} className="flex flex-col">
|
||||
<div
|
||||
className={`bg-theme-action-menu-bg border border-white/10 rounded-lg overflow-hidden transition-all duration-300 ${
|
||||
block.isExpanded ? "w-full" : "w-[280px] mx-auto"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
onClick={() => toggleBlockExpansion(block.id)}
|
||||
className="w-full p-4 flex items-center justify-between hover:bg-theme-action-menu-item-hover transition-colors duration-300 group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-7 h-7 rounded-lg bg-white/10 light:bg-white flex items-center justify-center">
|
||||
{React.cloneElement(BLOCK_INFO[block.type].icon, {
|
||||
className: "w-4 h-4 text-white",
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1 text-left min-w-0 max-w-[115px]">
|
||||
<span className="text-sm font-medium text-white block">
|
||||
{BLOCK_INFO[block.type].label}
|
||||
</span>
|
||||
{!block.isExpanded && (
|
||||
<p className="text-xs text-white/60 truncate">
|
||||
{BLOCK_INFO[block.type].getSummary(block.config)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{block.id !== "start" &&
|
||||
block.type !== BLOCK_TYPES.FINISH &&
|
||||
block.type !== BLOCK_TYPES.FLOW_INFO && (
|
||||
<div className="flex items-center gap-1">
|
||||
{index > 1 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveBlock(index, index - 1);
|
||||
}}
|
||||
className="p-1.5 rounded-lg bg-theme-bg-primary border border-white/5 text-white hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
data-tooltip-id="block-action"
|
||||
data-tooltip-content="Move block up"
|
||||
>
|
||||
<CaretUp className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
{index < blocks.length - 2 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
moveBlock(index, index + 1);
|
||||
}}
|
||||
className="p-1.5 rounded-lg bg-theme-bg-primary border border-white/5 text-white hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
data-tooltip-id="block-action"
|
||||
data-tooltip-content="Move block down"
|
||||
>
|
||||
<CaretDown className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeBlock(block.id);
|
||||
}}
|
||||
className="p-1.5 rounded-lg bg-theme-bg-primary border border-white/5 text-red-400 hover:bg-red-500/10 hover:border-red-500/20 transition-colors duration-300"
|
||||
data-tooltip-id="block-action"
|
||||
data-tooltip-content="Delete block"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`overflow-hidden transition-all duration-300 ease-in-out ${
|
||||
block.isExpanded
|
||||
? "max-h-[1000px] opacity-100"
|
||||
: "max-h-0 opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="border-t border-white/10 p-4 bg-theme-bg-secondary rounded-b-lg">
|
||||
{renderBlockConfig(block)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{index < blocks.length - 1 && (
|
||||
<div className="flex justify-center my-1">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="text-white/40 light:invert"
|
||||
>
|
||||
<path
|
||||
d="M12 4L12 20M12 20L6 14M12 20L18 14"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Tooltip
|
||||
id="block-action"
|
||||
place="bottom"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { BLOCK_TYPES, BLOCK_INFO };
|
130
frontend/src/pages/Admin/AgentBuilder/HeaderMenu/index.jsx
Normal file
130
frontend/src/pages/Admin/AgentBuilder/HeaderMenu/index.jsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
import { CaretDown, CaretUp, Plus } from "@phosphor-icons/react";
|
||||
import AnythingInfinityLogo from "@/media/logo/anything-llm-infinity.png";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function HeaderMenu({
|
||||
agentName,
|
||||
availableFlows = [],
|
||||
onNewFlow,
|
||||
onSaveFlow,
|
||||
}) {
|
||||
const { flowId = null } = useParams();
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const dropdownRef = useRef(null);
|
||||
const hasOtherFlows =
|
||||
availableFlows.filter((flow) => flow.uuid !== flowId).length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="absolute top-4 left-4 right-4">
|
||||
<div className="flex justify-between items-center max-w-[1700px] mx-auto">
|
||||
<div
|
||||
className="flex items-center bg-theme-settings-input-bg rounded-md border border-white/10 pointer-events-auto"
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<button
|
||||
onClick={() => navigate(paths.settings.agentSkills())}
|
||||
className="border-y-none border-l-none flex items-center gap-x-2 px-4 py-2 border-r border-white/10 hover:bg-theme-action-menu-bg transition-colors duration-300"
|
||||
>
|
||||
<img
|
||||
src={AnythingInfinityLogo}
|
||||
alt="logo"
|
||||
className="w-[20px] light:invert"
|
||||
/>
|
||||
<span className="text-theme-text-primary text-sm uppercase tracking-widest">
|
||||
Builder
|
||||
</span>
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
disabled={!hasOtherFlows}
|
||||
className="border-none flex items-center justify-between gap-x-1 text-theme-text-primary text-sm px-4 py-2 enabled:hover:bg-theme-action-menu-bg transition-colors duration-300 min-w-[200px] max-w-[300px]"
|
||||
onClick={() => {
|
||||
if (!agentName && !hasOtherFlows) {
|
||||
const agentNameInput = document.getElementById(
|
||||
"agent-flow-name-input"
|
||||
);
|
||||
if (agentNameInput) agentNameInput.focus();
|
||||
return;
|
||||
}
|
||||
setShowDropdown(!showDropdown);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={`text-sm font-medium truncate ${!!agentName ? "text-theme-text-primary " : "text-theme-text-secondary"}`}
|
||||
>
|
||||
{agentName || "Untitled Flow"}
|
||||
</span>
|
||||
{hasOtherFlows && (
|
||||
<div className="flex flex-col ml-2 shrink-0">
|
||||
<CaretUp size={10} />
|
||||
<CaretDown size={10} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
{showDropdown && (
|
||||
<div className="absolute top-full left-0 mt-1 w-full min-w-[200px] max-w-[350px] bg-theme-settings-input-bg border border-white/10 rounded-md shadow-lg z-50 animate-fadeUpIn">
|
||||
{availableFlows
|
||||
.filter((flow) => flow.uuid !== flowId)
|
||||
.map((flow) => (
|
||||
<button
|
||||
key={flow?.uuid || Math.random()}
|
||||
onClick={() => {
|
||||
navigate(paths.agents.editAgent(flow.uuid));
|
||||
setShowDropdown(false);
|
||||
}}
|
||||
className="border-none w-full text-left px-2 py-1 text-sm text-theme-text-primary hover:bg-theme-action-menu-bg transition-colors duration-300"
|
||||
>
|
||||
<span className="block truncate">
|
||||
{flow?.name || "Untitled Flow"}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-1 items-end">
|
||||
<div className="flex items-center gap-x-[15px]">
|
||||
<button
|
||||
onClick={onNewFlow}
|
||||
className="flex items-center gap-x-2 text-theme-text-primary text-sm font-medium px-3 py-2 rounded-lg border border-white bg-theme-settings-input-bg hover:bg-theme-action-menu-bg transition-colors duration-300"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
New Flow
|
||||
</button>
|
||||
<button
|
||||
onClick={onSaveFlow}
|
||||
className="border-none bg-primary-button hover:opacity-80 text-black px-3 py-2 rounded-lg text-sm font-medium transition-all duration-300 flex items-center justify-center gap-2"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<Link
|
||||
to="https://docs.anythingllm.com/agent-flows/overview"
|
||||
className="text-theme-text-secondary text-sm hover:underline hover:text-cta-button flex items-center gap-x-1 w-fit float-right"
|
||||
>
|
||||
view documentation →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
361
frontend/src/pages/Admin/AgentBuilder/index.jsx
Normal file
361
frontend/src/pages/Admin/AgentBuilder/index.jsx
Normal file
|
@ -0,0 +1,361 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import BlockList, { BLOCK_TYPES, BLOCK_INFO } from "./BlockList";
|
||||
import AddBlockMenu from "./AddBlockMenu";
|
||||
import showToast from "@/utils/toast";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
import HeaderMenu from "./HeaderMenu";
|
||||
import paths from "@/utils/paths";
|
||||
|
||||
const DEFAULT_BLOCKS = [
|
||||
{
|
||||
id: "flow_info",
|
||||
type: BLOCK_TYPES.FLOW_INFO,
|
||||
config: {
|
||||
name: "",
|
||||
description: "",
|
||||
},
|
||||
isExpanded: true,
|
||||
},
|
||||
{
|
||||
id: "start",
|
||||
type: BLOCK_TYPES.START,
|
||||
config: {
|
||||
variables: [{ name: "", value: "" }],
|
||||
},
|
||||
isExpanded: true,
|
||||
},
|
||||
{
|
||||
id: "finish",
|
||||
type: BLOCK_TYPES.FINISH,
|
||||
config: {},
|
||||
isExpanded: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default function AgentBuilder() {
|
||||
const { flowId } = useParams();
|
||||
const { theme } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [agentName, setAgentName] = useState("");
|
||||
const [_, setAgentDescription] = useState("");
|
||||
const [currentFlowUuid, setCurrentFlowUuid] = useState(null);
|
||||
const [active, setActive] = useState(true);
|
||||
const [blocks, setBlocks] = useState(DEFAULT_BLOCKS);
|
||||
const [selectedBlock, setSelectedBlock] = useState("start");
|
||||
const [showBlockMenu, setShowBlockMenu] = useState(false);
|
||||
const [showLoadMenu, setShowLoadMenu] = useState(false);
|
||||
const [availableFlows, setAvailableFlows] = useState([]);
|
||||
const [selectedFlowForDetails, setSelectedFlowForDetails] = useState(null);
|
||||
const nameRef = useRef(null);
|
||||
const descriptionRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadAvailableFlows();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (flowId) {
|
||||
loadFlow(flowId);
|
||||
}
|
||||
}, [flowId]);
|
||||
|
||||
useEffect(() => {
|
||||
const flowInfoBlock = blocks.find(
|
||||
(block) => block.type === BLOCK_TYPES.FLOW_INFO
|
||||
);
|
||||
setAgentName(flowInfoBlock?.config?.name || "");
|
||||
}, [blocks]);
|
||||
|
||||
const loadAvailableFlows = async () => {
|
||||
try {
|
||||
const { success, error, flows } = await AgentFlows.listFlows();
|
||||
if (!success) throw new Error(error);
|
||||
setAvailableFlows(flows);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showToast("Failed to load available flows", "error", { clear: true });
|
||||
}
|
||||
};
|
||||
|
||||
const loadFlow = async (uuid) => {
|
||||
try {
|
||||
const { success, error, flow } = await AgentFlows.getFlow(uuid);
|
||||
if (!success) throw new Error(error);
|
||||
|
||||
// Convert steps to blocks with IDs, ensuring finish block is at the end
|
||||
const flowBlocks = [
|
||||
{
|
||||
id: "flow_info",
|
||||
type: BLOCK_TYPES.FLOW_INFO,
|
||||
config: {
|
||||
name: flow.config.name,
|
||||
description: flow.config.description,
|
||||
},
|
||||
isExpanded: true,
|
||||
},
|
||||
...flow.config.steps.map((step, index) => ({
|
||||
id: index === 0 ? "start" : `block_${index}`,
|
||||
type: step.type,
|
||||
config: step.config,
|
||||
isExpanded: true,
|
||||
})),
|
||||
];
|
||||
|
||||
// Add finish block if not present
|
||||
if (flowBlocks[flowBlocks.length - 1]?.type !== BLOCK_TYPES.FINISH) {
|
||||
flowBlocks.push({
|
||||
id: "finish",
|
||||
type: BLOCK_TYPES.FINISH,
|
||||
config: {},
|
||||
isExpanded: false,
|
||||
});
|
||||
}
|
||||
|
||||
setAgentName(flow.config.name);
|
||||
setAgentDescription(flow.config.description);
|
||||
setActive(flow.config.active ?? true);
|
||||
setCurrentFlowUuid(flow.uuid);
|
||||
setBlocks(flowBlocks);
|
||||
setShowLoadMenu(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showToast("Failed to load flow", "error", { clear: true });
|
||||
}
|
||||
};
|
||||
|
||||
const addBlock = (type) => {
|
||||
const newBlock = {
|
||||
id: `block_${blocks.length}`,
|
||||
type,
|
||||
config: { ...BLOCK_INFO[type].defaultConfig },
|
||||
isExpanded: true,
|
||||
};
|
||||
// Insert the new block before the finish block
|
||||
const newBlocks = [...blocks];
|
||||
newBlocks.splice(newBlocks.length - 1, 0, newBlock);
|
||||
setBlocks(newBlocks);
|
||||
setShowBlockMenu(false);
|
||||
};
|
||||
|
||||
const updateBlockConfig = (blockId, config) => {
|
||||
setBlocks(
|
||||
blocks.map((block) =>
|
||||
block.id === blockId
|
||||
? { ...block, config: { ...block.config, ...config } }
|
||||
: block
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const removeBlock = (blockId) => {
|
||||
if (blockId === "start") return;
|
||||
setBlocks(blocks.filter((block) => block.id !== blockId));
|
||||
if (selectedBlock === blockId) {
|
||||
setSelectedBlock("start");
|
||||
}
|
||||
};
|
||||
|
||||
const saveFlow = async () => {
|
||||
const flowInfoBlock = blocks.find(
|
||||
(block) => block.type === BLOCK_TYPES.FLOW_INFO
|
||||
);
|
||||
const name = flowInfoBlock?.config?.name;
|
||||
const description = flowInfoBlock?.config?.description;
|
||||
|
||||
if (!name?.trim() || !description?.trim()) {
|
||||
// Make sure the flow info block is expanded first
|
||||
if (!flowInfoBlock.isExpanded) {
|
||||
setBlocks(
|
||||
blocks.map((block) =>
|
||||
block.type === BLOCK_TYPES.FLOW_INFO
|
||||
? { ...block, isExpanded: true }
|
||||
: block
|
||||
)
|
||||
);
|
||||
// Small delay to allow expansion animation to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
if (!name?.trim()) {
|
||||
nameRef.current?.focus();
|
||||
} else if (!description?.trim()) {
|
||||
descriptionRef.current?.focus();
|
||||
}
|
||||
showToast(
|
||||
"Please provide both a name and description for your flow",
|
||||
"error",
|
||||
{
|
||||
clear: true,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const flowConfig = {
|
||||
name,
|
||||
description,
|
||||
active,
|
||||
steps: blocks
|
||||
.filter(
|
||||
(block) =>
|
||||
block.type !== BLOCK_TYPES.FINISH &&
|
||||
block.type !== BLOCK_TYPES.FLOW_INFO
|
||||
)
|
||||
.map((block) => ({
|
||||
type: block.type,
|
||||
config: block.config,
|
||||
})),
|
||||
};
|
||||
|
||||
try {
|
||||
const { success, error, flow } = await AgentFlows.saveFlow(
|
||||
name,
|
||||
flowConfig,
|
||||
currentFlowUuid
|
||||
);
|
||||
if (!success) throw new Error(error);
|
||||
|
||||
setCurrentFlowUuid(flow.uuid);
|
||||
showToast("Agent flow saved successfully!", "success", { clear: true });
|
||||
await loadAvailableFlows();
|
||||
} catch (error) {
|
||||
console.error("Save error details:", error);
|
||||
showToast("Failed to save agent flow", "error", { clear: true });
|
||||
}
|
||||
};
|
||||
|
||||
const toggleBlockExpansion = (blockId) => {
|
||||
setBlocks(
|
||||
blocks.map((block) =>
|
||||
block.id === blockId
|
||||
? { ...block, isExpanded: !block.isExpanded }
|
||||
: block
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Get all available variables from the start block
|
||||
const getAvailableVariables = () => {
|
||||
const startBlock = blocks.find((b) => b.type === BLOCK_TYPES.START);
|
||||
return startBlock?.config?.variables?.filter((v) => v.name) || [];
|
||||
};
|
||||
|
||||
const renderVariableSelect = (
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Select variable"
|
||||
) => (
|
||||
<select
|
||||
value={value || ""}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
>
|
||||
<option value="" className="bg-theme-bg-primary">
|
||||
{placeholder}
|
||||
</option>
|
||||
{getAvailableVariables().map((v) => (
|
||||
<option key={v.name} value={v.name} className="bg-theme-bg-primary">
|
||||
{v.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
|
||||
const deleteVariable = (variableName) => {
|
||||
// Clean up references in other blocks
|
||||
blocks.forEach((block) => {
|
||||
if (block.type === BLOCK_TYPES.START) return;
|
||||
|
||||
let configUpdated = false;
|
||||
const newConfig = { ...block.config };
|
||||
|
||||
// Check and clean responseVariable/resultVariable
|
||||
if (newConfig.responseVariable === variableName) {
|
||||
newConfig.responseVariable = "";
|
||||
configUpdated = true;
|
||||
}
|
||||
if (newConfig.resultVariable === variableName) {
|
||||
newConfig.resultVariable = "";
|
||||
configUpdated = true;
|
||||
}
|
||||
|
||||
if (configUpdated) {
|
||||
updateBlockConfig(block.id, newConfig);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// const runFlow = async (uuid) => {
|
||||
// try {
|
||||
// const { success, error, _results } = await AgentFlows.runFlow(uuid);
|
||||
// if (!success) throw new Error(error);
|
||||
|
||||
// showToast("Flow executed successfully!", "success", { clear: true });
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// showToast("Failed to run agent flow", "error", { clear: true });
|
||||
// }
|
||||
// };
|
||||
|
||||
const clearFlow = () => {
|
||||
if (!!flowId) navigate(paths.agents.builder());
|
||||
setAgentName("");
|
||||
setAgentDescription("");
|
||||
setCurrentFlowUuid(null);
|
||||
setActive(true);
|
||||
setBlocks(DEFAULT_BLOCKS);
|
||||
};
|
||||
|
||||
const moveBlock = (fromIndex, toIndex) => {
|
||||
const newBlocks = [...blocks];
|
||||
const [movedBlock] = newBlocks.splice(fromIndex, 1);
|
||||
newBlocks.splice(toIndex, 0, movedBlock);
|
||||
setBlocks(newBlocks);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundImage:
|
||||
theme === "light"
|
||||
? "radial-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 0)"
|
||||
: "radial-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 0)",
|
||||
backgroundSize: "15px 15px",
|
||||
backgroundPosition: "-7.5px -7.5px",
|
||||
}}
|
||||
className="w-full h-screen flex bg-theme-bg-primary"
|
||||
>
|
||||
<div className="w-full flex flex-col">
|
||||
<HeaderMenu
|
||||
agentName={agentName}
|
||||
availableFlows={availableFlows}
|
||||
onNewFlow={clearFlow}
|
||||
onSaveFlow={saveFlow}
|
||||
/>
|
||||
<div className="flex-1 p-6 overflow-y-auto">
|
||||
<div className="max-w-xl mx-auto mt-14">
|
||||
<BlockList
|
||||
blocks={blocks}
|
||||
updateBlockConfig={updateBlockConfig}
|
||||
removeBlock={removeBlock}
|
||||
toggleBlockExpansion={toggleBlockExpansion}
|
||||
renderVariableSelect={renderVariableSelect}
|
||||
onDeleteVariable={deleteVariable}
|
||||
moveBlock={moveBlock}
|
||||
refs={{ nameRef, descriptionRef }}
|
||||
/>
|
||||
|
||||
<AddBlockMenu
|
||||
showBlockMenu={showBlockMenu}
|
||||
setShowBlockMenu={setShowBlockMenu}
|
||||
addBlock={addBlock}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { Plus, X, CaretDown } from "@phosphor-icons/react";
|
||||
|
||||
export default function ApiCallNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
const urlInputRef = useRef(null);
|
||||
const [showVarMenu, setShowVarMenu] = useState(false);
|
||||
const varButtonRef = useRef(null);
|
||||
|
||||
const handleHeaderChange = (index, field, value) => {
|
||||
const newHeaders = [...(config.headers || [])];
|
||||
newHeaders[index] = { ...newHeaders[index], [field]: value };
|
||||
onConfigChange({ headers: newHeaders });
|
||||
};
|
||||
|
||||
const addHeader = () => {
|
||||
const newHeaders = [...(config.headers || []), { key: "", value: "" }];
|
||||
onConfigChange({ headers: newHeaders });
|
||||
};
|
||||
|
||||
const removeHeader = (index) => {
|
||||
const newHeaders = [...(config.headers || [])].filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
onConfigChange({ headers: newHeaders });
|
||||
};
|
||||
|
||||
const insertVariableAtCursor = (variableName) => {
|
||||
if (!urlInputRef.current) return;
|
||||
|
||||
const input = urlInputRef.current;
|
||||
const start = input.selectionStart;
|
||||
const end = input.selectionEnd;
|
||||
const currentValue = config.url;
|
||||
|
||||
const newValue =
|
||||
currentValue.substring(0, start) +
|
||||
"${" +
|
||||
variableName +
|
||||
"}" +
|
||||
currentValue.substring(end);
|
||||
|
||||
onConfigChange({ url: newValue });
|
||||
setShowVarMenu(false);
|
||||
|
||||
// Set cursor position after the inserted variable
|
||||
setTimeout(() => {
|
||||
const newPosition = start + variableName.length + 3; // +3 for ${}
|
||||
input.setSelectionRange(newPosition, newPosition);
|
||||
input.focus();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
URL
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
ref={urlInputRef}
|
||||
type="text"
|
||||
placeholder="https://api.example.com/endpoint"
|
||||
value={config.url}
|
||||
onChange={(e) => onConfigChange({ url: e.target.value })}
|
||||
className="flex-1 border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<div className="relative">
|
||||
<button
|
||||
ref={varButtonRef}
|
||||
onClick={() => setShowVarMenu(!showVarMenu)}
|
||||
className="h-full px-3 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:bg-theme-action-menu-item-hover transition-colors duration-300 flex items-center gap-1"
|
||||
title="Insert variable"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<CaretDown className="w-3 h-3" />
|
||||
</button>
|
||||
{showVarMenu && (
|
||||
<div className="absolute right-0 top-[calc(100%+4px)] w-48 bg-theme-settings-input-bg border-none rounded-lg shadow-lg z-10">
|
||||
{renderVariableSelect(
|
||||
"",
|
||||
insertVariableAtCursor,
|
||||
"Select variable to insert",
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Method
|
||||
</label>
|
||||
<select
|
||||
value={config.method}
|
||||
onChange={(e) => onConfigChange({ method: e.target.value })}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
>
|
||||
{["GET", "POST", "DELETE"].map((method) => (
|
||||
<option
|
||||
key={method}
|
||||
value={method}
|
||||
className="bg-theme-settings-input-bg"
|
||||
>
|
||||
{method}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-theme-text-primary">
|
||||
Headers
|
||||
</label>
|
||||
<button
|
||||
onClick={addHeader}
|
||||
className="p-1.5 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
title="Add header"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{(config.headers || []).map((header, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Header name"
|
||||
value={header.key}
|
||||
onChange={(e) =>
|
||||
handleHeaderChange(index, "key", e.target.value)
|
||||
}
|
||||
className="flex-1 border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={header.value}
|
||||
onChange={(e) =>
|
||||
handleHeaderChange(index, "value", e.target.value)
|
||||
}
|
||||
className="flex-1 border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<button
|
||||
onClick={() => removeHeader(index)}
|
||||
className="p-2.5 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:text-red-500 hover:border-red-500/20 hover:bg-red-500/10 transition-colors duration-300"
|
||||
title="Remove header"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{["POST", "PUT", "PATCH"].includes(config.method) && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Request Body
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
<select
|
||||
value={config.bodyType || "json"}
|
||||
onChange={(e) => onConfigChange({ bodyType: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-theme-text-primary focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none light:bg-theme-settings-input-bg light:border-black/10"
|
||||
>
|
||||
<option
|
||||
value="json"
|
||||
className="bg-theme-bg-primary light:bg-theme-settings-input-bg"
|
||||
>
|
||||
JSON
|
||||
</option>
|
||||
<option
|
||||
value="text"
|
||||
className="bg-theme-bg-primary light:bg-theme-settings-input-bg"
|
||||
>
|
||||
Raw Text
|
||||
</option>
|
||||
<option
|
||||
value="form"
|
||||
className="bg-theme-bg-primary light:bg-theme-settings-input-bg"
|
||||
>
|
||||
Form Data
|
||||
</option>
|
||||
</select>
|
||||
{config.bodyType === "json" ? (
|
||||
<textarea
|
||||
placeholder='{"key": "value"}'
|
||||
value={config.body}
|
||||
onChange={(e) => onConfigChange({ body: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-theme-text-primary placeholder:text-theme-text-secondary/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none light:bg-theme-settings-input-bg light:border-black/10 font-mono"
|
||||
rows={4}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
) : config.bodyType === "form" ? (
|
||||
<div className="space-y-2">
|
||||
{(config.formData || []).map((item, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={item.key}
|
||||
onChange={(e) => {
|
||||
const newFormData = [...(config.formData || [])];
|
||||
newFormData[index] = { ...item, key: e.target.value };
|
||||
onConfigChange({ formData: newFormData });
|
||||
}}
|
||||
className="flex-1 p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-theme-text-primary placeholder:text-theme-text-secondary/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none light:bg-theme-settings-input-bg light:border-black/10"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
const newFormData = [...(config.formData || [])];
|
||||
newFormData[index] = { ...item, value: e.target.value };
|
||||
onConfigChange({ formData: newFormData });
|
||||
}}
|
||||
className="flex-1 p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-theme-text-primary placeholder:text-theme-text-secondary/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none light:bg-theme-settings-input-bg light:border-black/10"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newFormData = [...(config.formData || [])].filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
onConfigChange({ formData: newFormData });
|
||||
}}
|
||||
className="p-2.5 rounded-lg bg-theme-bg-primary border border-white/5 text-theme-text-primary hover:text-red-500 hover:border-red-500/20 hover:bg-red-500/10 transition-colors duration-300 light:bg-theme-settings-input-bg light:border-black/10"
|
||||
title="Remove field"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() => {
|
||||
const newFormData = [
|
||||
...(config.formData || []),
|
||||
{ key: "", value: "" },
|
||||
];
|
||||
onConfigChange({ formData: newFormData });
|
||||
}}
|
||||
className="w-full p-2.5 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:bg-theme-action-menu-item-hover transition-colors duration-300 text-sm"
|
||||
>
|
||||
Add Form Field
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
placeholder="Raw request body..."
|
||||
value={config.body}
|
||||
onChange={(e) => onConfigChange({ body: e.target.value })}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
rows={4}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Store Response In
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.responseVariable,
|
||||
(value) => onConfigChange({ responseVariable: value }),
|
||||
"Select or create variable"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import React from "react";
|
||||
|
||||
export default function CodeNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Language
|
||||
</label>
|
||||
<select
|
||||
value={config.language}
|
||||
onChange={(e) => onConfigChange({ language: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
>
|
||||
<option value="javascript" className="bg-theme-bg-primary">
|
||||
JavaScript
|
||||
</option>
|
||||
<option value="python" className="bg-theme-bg-primary">
|
||||
Python
|
||||
</option>
|
||||
<option value="shell" className="bg-theme-bg-primary">
|
||||
Shell
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Code
|
||||
</label>
|
||||
<textarea
|
||||
placeholder="Enter code..."
|
||||
value={config.code}
|
||||
onChange={(e) => onConfigChange({ code: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white placeholder:text-white/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none font-mono"
|
||||
rows={5}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Store Result In
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.resultVariable,
|
||||
(value) => onConfigChange({ resultVariable: value }),
|
||||
"Select or create variable"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import React from "react";
|
||||
|
||||
export default function FileNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Operation
|
||||
</label>
|
||||
<select
|
||||
value={config.operation}
|
||||
onChange={(e) => onConfigChange({ operation: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
>
|
||||
<option value="read" className="bg-theme-bg-primary">
|
||||
Read File
|
||||
</option>
|
||||
<option value="write" className="bg-theme-bg-primary">
|
||||
Write File
|
||||
</option>
|
||||
<option value="append" className="bg-theme-bg-primary">
|
||||
Append to File
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
File Path
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="/path/to/file"
|
||||
value={config.path}
|
||||
onChange={(e) => onConfigChange({ path: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white placeholder:text-white/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
{config.operation !== "read" && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Content
|
||||
</label>
|
||||
<textarea
|
||||
placeholder="File content..."
|
||||
value={config.content}
|
||||
onChange={(e) => onConfigChange({ content: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white placeholder:text-white/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
rows={3}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Store Result In
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.resultVariable,
|
||||
(value) => onConfigChange({ resultVariable: value }),
|
||||
"Select or create variable"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
|
||||
export default function FinishNode() {
|
||||
return (
|
||||
<div className="text-sm text-white/60">
|
||||
This is the end of your agent flow. All steps above will be executed in
|
||||
sequence.
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import React, { forwardRef } from "react";
|
||||
|
||||
const FlowInfoNode = forwardRef(({ config, onConfigChange }, refs) => {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Flow Name
|
||||
</label>
|
||||
<div className="flex flex-col text-xs text-theme-text-secondary mt-2 mb-3">
|
||||
<p className="">
|
||||
It is important to give your flow a name that an LLM can easily
|
||||
understand.
|
||||
</p>
|
||||
<p>"SendMessageToDiscord", "CheckStockPrice", "CheckWeather"</p>
|
||||
</div>
|
||||
<input
|
||||
id="agent-flow-name-input"
|
||||
ref={refs?.nameRef}
|
||||
type="text"
|
||||
placeholder="Enter flow name"
|
||||
value={config?.name || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange({
|
||||
...config,
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Description
|
||||
</label>
|
||||
<div className="flex flex-col text-xs text-theme-text-secondary mt-2 mb-3">
|
||||
<p className="">
|
||||
It is equally important to give your flow a description that an LLM
|
||||
can easily understand. Be sure to include the purpose of the flow,
|
||||
the context it will be used in, and any other relevant information.
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
ref={refs?.descriptionRef}
|
||||
value={config?.description || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange({
|
||||
...config,
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
rows={3}
|
||||
placeholder="Enter flow description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
FlowInfoNode.displayName = "FlowInfoNode";
|
||||
export default FlowInfoNode;
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
|
||||
export default function LLMInstructionNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Input Variable
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.inputVariable,
|
||||
(value) => onConfigChange({ ...config, inputVariable: value }),
|
||||
"Select input variable"
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Instruction
|
||||
</label>
|
||||
<textarea
|
||||
value={config?.instruction || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange({
|
||||
...config,
|
||||
instruction: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
rows={3}
|
||||
placeholder="Enter instructions for the LLM..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Result Variable
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.resultVariable,
|
||||
(value) => onConfigChange({ ...config, resultVariable: value }),
|
||||
"Select or create variable",
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import React from "react";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
|
||||
export default function StartNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
onDeleteVariable,
|
||||
}) {
|
||||
const handleDeleteVariable = (index, variableName) => {
|
||||
// First clean up references, then delete the variable
|
||||
onDeleteVariable(variableName);
|
||||
const newVars = config.variables.filter((_, i) => i !== index);
|
||||
onConfigChange({ variables: newVars });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-theme-text-primary">Variables</h3>
|
||||
{config.variables.map((variable, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Variable name"
|
||||
value={variable.name}
|
||||
onChange={(e) => {
|
||||
const newVars = [...config.variables];
|
||||
newVars[index].name = e.target.value;
|
||||
onConfigChange({ variables: newVars });
|
||||
}}
|
||||
className="flex-1 border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Initial value"
|
||||
value={variable.value}
|
||||
onChange={(e) => {
|
||||
const newVars = [...config.variables];
|
||||
newVars[index].value = e.target.value;
|
||||
onConfigChange({ variables: newVars });
|
||||
}}
|
||||
className="flex-1 border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
{config.variables.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleDeleteVariable(index, variable.name)}
|
||||
className="p-2.5 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:text-red-500 hover:border-red-500/20 hover:bg-red-500/10 transition-colors duration-300"
|
||||
title="Delete variable"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{index === config.variables.length - 1 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
const newVars = [...config.variables, { name: "", value: "" }];
|
||||
onConfigChange({ variables: newVars });
|
||||
}}
|
||||
className="p-2.5 rounded-lg border-none bg-theme-settings-input-bg text-theme-text-primary hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
title="Add variable"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import React from "react";
|
||||
|
||||
export default function WebScrapingNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
URL to Scrape
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={config?.url || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange({
|
||||
...config,
|
||||
url: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none p-2.5"
|
||||
placeholder="https://example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-theme-text-primary mb-2">
|
||||
Result Variable
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.resultVariable,
|
||||
(value) => onConfigChange({ ...config, resultVariable: value }),
|
||||
"Select or create variable",
|
||||
true
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import React from "react";
|
||||
|
||||
export default function WebsiteNode({
|
||||
config,
|
||||
onConfigChange,
|
||||
renderVariableSelect,
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">URL</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://example.com"
|
||||
value={config.url}
|
||||
onChange={(e) => onConfigChange({ url: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white placeholder:text-white/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Action
|
||||
</label>
|
||||
<select
|
||||
value={config.action}
|
||||
onChange={(e) => onConfigChange({ action: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
>
|
||||
<option value="read" className="bg-theme-bg-primary">
|
||||
Read Content
|
||||
</option>
|
||||
<option value="click" className="bg-theme-bg-primary">
|
||||
Click Element
|
||||
</option>
|
||||
<option value="type" className="bg-theme-bg-primary">
|
||||
Type Text
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
CSS Selector
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="#element-id or .class-name"
|
||||
value={config.selector}
|
||||
onChange={(e) => onConfigChange({ selector: e.target.value })}
|
||||
className="w-full p-2.5 text-sm rounded-lg bg-theme-bg-primary border border-white/5 text-white placeholder:text-white/20 focus:border-primary-button focus:ring-1 focus:ring-primary-button outline-none"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
Store Result In
|
||||
</label>
|
||||
{renderVariableSelect(
|
||||
config.resultVariable,
|
||||
(value) => onConfigChange({ resultVariable: value }),
|
||||
"Select or create variable"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
124
frontend/src/pages/Admin/Agents/AgentFlows/FlowPanel.jsx
Normal file
124
frontend/src/pages/Admin/Agents/AgentFlows/FlowPanel.jsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
import showToast from "@/utils/toast";
|
||||
import { FlowArrow, Gear } from "@phosphor-icons/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
|
||||
function ManageFlowMenu({ flow, onDelete }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const menuRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function deleteFlow() {
|
||||
if (
|
||||
!window.confirm(
|
||||
"Are you sure you want to delete this flow? This action cannot be undone."
|
||||
)
|
||||
)
|
||||
return;
|
||||
const { success, error } = await AgentFlows.deleteFlow(flow.uuid);
|
||||
if (success) {
|
||||
showToast("Flow deleted successfully.", "success");
|
||||
onDelete(flow.uuid);
|
||||
} else {
|
||||
showToast(error || "Failed to delete flow.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative" ref={menuRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="p-1.5 rounded-lg text-white hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
>
|
||||
<Gear className="h-5 w-5" weight="bold" />
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute w-[100px] -top-1 left-7 mt-1 border-[1.5px] border-white/40 rounded-lg bg-theme-action-menu-bg flex flex-col shadow-[0_4px_14px_rgba(0,0,0,0.25)] text-white z-99 md:z-10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(paths.agents.editAgent(flow.uuid))}
|
||||
className="border-none flex items-center rounded-lg gap-x-2 hover:bg-theme-action-menu-item-hover py-1.5 px-2 transition-colors duration-200 w-full text-left"
|
||||
>
|
||||
<span className="text-sm">Edit Flow</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteFlow}
|
||||
className="border-none flex items-center rounded-lg gap-x-2 hover:bg-theme-action-menu-item-hover py-1.5 px-2 transition-colors duration-200 w-full text-left"
|
||||
>
|
||||
<span className="text-sm">Delete Flow</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FlowPanel({ flow, toggleFlow, onDelete }) {
|
||||
const [isActive, setIsActive] = useState(flow.active);
|
||||
|
||||
useEffect(() => {
|
||||
setIsActive(flow.active);
|
||||
}, [flow.uuid, flow.active]);
|
||||
|
||||
const handleToggle = async () => {
|
||||
try {
|
||||
const { success, error } = await AgentFlows.toggleFlow(
|
||||
flow.uuid,
|
||||
!isActive
|
||||
);
|
||||
if (!success) throw new Error(error);
|
||||
setIsActive(!isActive);
|
||||
toggleFlow(flow.uuid);
|
||||
showToast("Flow status updated successfully", "success", { clear: true });
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle flow:", error);
|
||||
showToast("Failed to toggle flow", "error", { clear: true });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-2">
|
||||
<div className="flex flex-col gap-y-[18px] max-w-[500px]">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<FlowArrow size={24} weight="bold" className="text-white" />
|
||||
<label htmlFor="name" className="text-white text-md font-bold">
|
||||
{flow.name}
|
||||
</label>
|
||||
<label className="border-none relative inline-flex items-center ml-auto cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="peer sr-only"
|
||||
checked={isActive}
|
||||
onChange={handleToggle}
|
||||
/>
|
||||
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
|
||||
<span className="ml-3 text-sm font-medium"></span>
|
||||
</label>
|
||||
<ManageFlowMenu flow={flow} onDelete={onDelete} />
|
||||
</div>
|
||||
<p className="whitespace-pre-wrap text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
{flow.description || "No description provided"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
57
frontend/src/pages/Admin/Agents/AgentFlows/index.jsx
Normal file
57
frontend/src/pages/Admin/Agents/AgentFlows/index.jsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React from "react";
|
||||
import { CaretRight } from "@phosphor-icons/react";
|
||||
|
||||
export default function AgentFlowsList({
|
||||
flows = [],
|
||||
selectedFlow,
|
||||
handleClick,
|
||||
}) {
|
||||
if (flows.length === 0) {
|
||||
return (
|
||||
<div className="text-theme-text-secondary text-center text-xs flex flex-col gap-y-2">
|
||||
<p>No agent flows found</p>
|
||||
<a
|
||||
href="https://docs.anythingllm.com/agent-flows/getting-started"
|
||||
target="_blank"
|
||||
className="text-theme-text-secondary underline hover:text-cta-button"
|
||||
>
|
||||
Learn more about Agent Flows.
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-theme-bg-secondary text-white rounded-xl min-w-[360px] w-fit">
|
||||
{flows.map((flow, index) => (
|
||||
<div
|
||||
key={flow.uuid}
|
||||
className={`py-3 px-4 flex items-center justify-between ${
|
||||
index === 0 ? "rounded-t-xl" : ""
|
||||
} ${
|
||||
index === flows.length - 1
|
||||
? "rounded-b-xl"
|
||||
: "border-b border-white/10"
|
||||
} cursor-pointer transition-all duration-300 hover:bg-theme-bg-primary ${
|
||||
selectedFlow?.uuid === flow.uuid
|
||||
? "bg-white/10 light:bg-theme-bg-sidebar"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => handleClick?.(flow)}
|
||||
>
|
||||
<div className="text-sm font-light">{flow.name}</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="text-sm text-theme-text-secondary font-medium">
|
||||
{flow.active ? "On" : "Off"}
|
||||
</div>
|
||||
<CaretRight
|
||||
size={14}
|
||||
weight="bold"
|
||||
className="text-theme-text-secondary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -231,9 +231,9 @@ function ManageSkillMenu({ config, setImportedSkills }) {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className={`border-none transition duration-200 hover:rotate-90 outline-none ring-none ${open ? "rotate-90" : ""}`}
|
||||
className="p-1.5 rounded-lg text-white hover:bg-theme-action-menu-item-hover transition-colors duration-300"
|
||||
>
|
||||
<Gear size={24} weight="bold" />
|
||||
<Gear className="h-5 w-5" weight="bold" />
|
||||
</button>
|
||||
{open && (
|
||||
<div className="absolute w-[100px] -top-1 left-7 mt-1 border-[1.5px] border-white/40 rounded-lg bg-theme-action-menu-bg flex flex-col shadow-[0_4px_14px_rgba(0,0,0,0.25)] text-white z-99 md:z-10">
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function ImportedSkillList({
|
|||
<a
|
||||
href="https://docs.anythingllm.com/agent/custom/developer-guide"
|
||||
target="_blank"
|
||||
className="text-theme-text-secondary light:underline hover:underline"
|
||||
className="text-theme-text-secondary underline hover:text-cta-button"
|
||||
>
|
||||
AnythingLLM Agent Docs
|
||||
</a>
|
||||
|
|
|
@ -4,7 +4,15 @@ import { isMobile } from "react-device-detect";
|
|||
import Admin from "@/models/admin";
|
||||
import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import { CaretLeft, CaretRight, Plug, Robot } from "@phosphor-icons/react";
|
||||
import {
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
Plug,
|
||||
Robot,
|
||||
Hammer,
|
||||
FlowArrow,
|
||||
PlusCircle,
|
||||
} from "@phosphor-icons/react";
|
||||
import ContextualSaveBar from "@/components/ContextualSaveBar";
|
||||
import { castToType } from "@/utils/types";
|
||||
import { FullScreenLoader } from "@/components/Preloader";
|
||||
|
@ -13,6 +21,11 @@ import { DefaultBadge } from "./Badges/default";
|
|||
import ImportedSkillList from "./Imported/SkillList";
|
||||
import ImportedSkillConfig from "./Imported/ImportedSkillConfig";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import AgentFlowsList from "./AgentFlows";
|
||||
import FlowPanel from "./AgentFlows/FlowPanel";
|
||||
import { Link } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import AgentFlows from "@/models/agentFlows";
|
||||
|
||||
export default function AdminAgents() {
|
||||
const formEl = useRef(null);
|
||||
|
@ -26,6 +39,10 @@ export default function AdminAgents() {
|
|||
const [importedSkills, setImportedSkills] = useState([]);
|
||||
const [disabledAgentSkills, setDisabledAgentSkills] = useState([]);
|
||||
|
||||
const [agentFlows, setAgentFlows] = useState([]);
|
||||
const [selectedFlow, setSelectedFlow] = useState(null);
|
||||
const [activeFlowIds, setActiveFlowIds] = useState([]);
|
||||
|
||||
// Alert user if they try to leave the page with unsaved changes
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (event) => {
|
||||
|
@ -47,13 +64,17 @@ export default function AdminAgents() {
|
|||
"disabled_agent_skills",
|
||||
"default_agent_skills",
|
||||
"imported_agent_skills",
|
||||
"active_agent_flows",
|
||||
]);
|
||||
const { flows = [] } = await AgentFlows.listFlows();
|
||||
setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
|
||||
setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
|
||||
setDisabledAgentSkills(
|
||||
_preferences.settings?.disabled_agent_skills ?? []
|
||||
);
|
||||
setImportedSkills(_preferences.settings?.imported_agent_skills ?? []);
|
||||
setActiveFlowIds(_preferences.settings?.active_agent_flows ?? []);
|
||||
setAgentFlows(flows);
|
||||
setLoading(false);
|
||||
}
|
||||
fetchSettings();
|
||||
|
@ -79,6 +100,15 @@ export default function AdminAgents() {
|
|||
});
|
||||
};
|
||||
|
||||
const toggleFlow = (flowId) => {
|
||||
setActiveFlowIds((prev) => {
|
||||
const updatedFlows = prev.includes(flowId)
|
||||
? prev.filter((id) => id !== flowId)
|
||||
: [...prev, flowId];
|
||||
return updatedFlows;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
|
@ -129,10 +159,30 @@ export default function AdminAgents() {
|
|||
setHasChanges(false);
|
||||
};
|
||||
|
||||
const SelectedSkillComponent = selectedSkill.imported
|
||||
? ImportedSkillConfig
|
||||
: configurableSkills[selectedSkill]?.component ||
|
||||
defaultSkills[selectedSkill]?.component;
|
||||
const SelectedSkillComponent = selectedFlow
|
||||
? FlowPanel
|
||||
: selectedSkill?.imported
|
||||
? ImportedSkillConfig
|
||||
: configurableSkills[selectedSkill]?.component ||
|
||||
defaultSkills[selectedSkill]?.component;
|
||||
|
||||
// Update the click handlers to clear the other selection
|
||||
const handleSkillClick = (skill) => {
|
||||
setSelectedFlow(null);
|
||||
setSelectedSkill(skill);
|
||||
if (isMobile) setShowSkillModal(true);
|
||||
};
|
||||
|
||||
const handleFlowClick = (flow) => {
|
||||
setSelectedSkill(null);
|
||||
setSelectedFlow(flow);
|
||||
};
|
||||
|
||||
const handleFlowDelete = (flowId) => {
|
||||
setSelectedFlow(null);
|
||||
setActiveFlowIds((prev) => prev.filter((id) => id !== flowId));
|
||||
setAgentFlows((prev) => prev.filter((flow) => flow.uuid !== flowId));
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
@ -154,7 +204,7 @@ export default function AdminAgents() {
|
|||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
onChange={() => setHasChanges(true)}
|
||||
onChange={() => !selectedFlow && setHasChanges(true)}
|
||||
ref={formEl}
|
||||
className="flex flex-col w-full p-4 mt-10"
|
||||
>
|
||||
|
@ -180,6 +230,7 @@ export default function AdminAgents() {
|
|||
skills={defaultSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={(skill) => {
|
||||
setSelectedFlow(null);
|
||||
setSelectedSkill(skill);
|
||||
setShowSkillModal(true);
|
||||
}}
|
||||
|
@ -192,6 +243,7 @@ export default function AdminAgents() {
|
|||
skills={configurableSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={(skill) => {
|
||||
setSelectedFlow(null);
|
||||
setSelectedSkill(skill);
|
||||
setShowSkillModal(true);
|
||||
}}
|
||||
|
@ -205,7 +257,23 @@ export default function AdminAgents() {
|
|||
<ImportedSkillList
|
||||
skills={importedSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={setSelectedSkill}
|
||||
handleClick={handleSkillClick}
|
||||
/>
|
||||
|
||||
<div className="text-theme-text-primary flex items-center gap-x-2 mt-6">
|
||||
<FlowArrow size={24} />
|
||||
<p className="text-lg font-medium">Agent Flows</p>
|
||||
</div>
|
||||
<AgentFlowsList
|
||||
flows={agentFlows}
|
||||
selectedFlow={selectedFlow}
|
||||
handleClick={handleFlowClick}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="system::active_agent_flows"
|
||||
id="active_agent_flows"
|
||||
value={activeFlowIds.join(",")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -232,7 +300,14 @@ export default function AdminAgents() {
|
|||
<div className=" bg-theme-bg-secondary text-white rounded-xl p-4">
|
||||
{SelectedSkillComponent ? (
|
||||
<>
|
||||
{selectedSkill.imported ? (
|
||||
{selectedFlow ? (
|
||||
<FlowPanel
|
||||
flow={selectedFlow}
|
||||
toggleFlow={toggleFlow}
|
||||
enabled={activeFlowIds.includes(selectedFlow.uuid)}
|
||||
onDelete={handleFlowDelete}
|
||||
/>
|
||||
) : selectedSkill.imported ? (
|
||||
<ImportedSkillConfig
|
||||
key={selectedSkill.hubId}
|
||||
selectedSkill={selectedSkill}
|
||||
|
@ -273,7 +348,9 @@ export default function AdminAgents() {
|
|||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-theme-text-secondary">
|
||||
<Robot size={40} />
|
||||
<p className="font-medium">Select an agent skill</p>
|
||||
<p className="font-medium">
|
||||
Select an agent skill or flow
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -294,7 +371,9 @@ export default function AdminAgents() {
|
|||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
onChange={() => !selectedSkill.imported && setHasChanges(true)}
|
||||
onChange={() =>
|
||||
!selectedSkill?.imported && !selectedFlow && setHasChanges(true)
|
||||
}
|
||||
ref={formEl}
|
||||
className="flex-1 flex gap-x-6 p-4 mt-10"
|
||||
>
|
||||
|
@ -308,40 +387,81 @@ export default function AdminAgents() {
|
|||
type="hidden"
|
||||
value={disabledAgentSkills.join(",")}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="system::active_agent_flows"
|
||||
id="active_agent_flows"
|
||||
value={activeFlowIds.join(",")}
|
||||
/>
|
||||
|
||||
{/* Skill settings nav */}
|
||||
<div className="flex flex-col gap-y-[18px]">
|
||||
<div className="text-theme-text-primary flex items-center gap-x-2">
|
||||
<Robot size={24} />
|
||||
<p className="text-lg font-medium">Agent Skills</p>
|
||||
{/* Skill settings nav - Make this section scrollable */}
|
||||
<div className="flex flex-col min-w-[360px] h-[calc(100vh-90px)]">
|
||||
<div className="flex-none mb-4">
|
||||
<div className="text-theme-text-primary flex items-center gap-x-2">
|
||||
<Robot size={24} />
|
||||
<p className="text-lg font-medium">Agent Skills</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Default skills list */}
|
||||
<SkillList
|
||||
skills={defaultSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={setSelectedSkill}
|
||||
activeSkills={Object.keys(defaultSkills).filter(
|
||||
(skill) => !disabledAgentSkills.includes(skill)
|
||||
)}
|
||||
/>
|
||||
{/* Configurable skills */}
|
||||
<SkillList
|
||||
skills={configurableSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={setSelectedSkill}
|
||||
activeSkills={agentSkills}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto pr-2 pb-4">
|
||||
<div className="space-y-4">
|
||||
{/* Default skills list */}
|
||||
<SkillList
|
||||
skills={defaultSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={handleSkillClick}
|
||||
activeSkills={Object.keys(defaultSkills).filter(
|
||||
(skill) => !disabledAgentSkills.includes(skill)
|
||||
)}
|
||||
/>
|
||||
{/* Configurable skills */}
|
||||
<SkillList
|
||||
skills={configurableSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={handleSkillClick}
|
||||
activeSkills={agentSkills}
|
||||
/>
|
||||
|
||||
<div className="text-theme-text-primary flex items-center gap-x-2">
|
||||
<Plug size={24} />
|
||||
<p className="text-lg font-medium">Custom Skills</p>
|
||||
<div className="text-theme-text-primary flex items-center gap-x-2 mt-4">
|
||||
<Plug size={24} />
|
||||
<p className="text-lg font-medium">Custom Skills</p>
|
||||
</div>
|
||||
<ImportedSkillList
|
||||
skills={importedSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={handleSkillClick}
|
||||
/>
|
||||
|
||||
<div className="text-theme-text-primary flex items-center justify-between gap-x-2 mt-4">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<FlowArrow size={24} />
|
||||
<p className="text-lg font-medium">Agent Flows</p>
|
||||
</div>
|
||||
{agentFlows.length === 0 ? (
|
||||
<Link
|
||||
to={paths.agents.builder()}
|
||||
className="text-cta-button flex items-center gap-x-1 hover:underline"
|
||||
>
|
||||
<Hammer size={16} />
|
||||
<p className="text-sm">Create Flow</p>
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to={paths.agents.builder()}
|
||||
className="text-theme-text-secondary hover:text-cta-button flex items-center gap-x-1"
|
||||
>
|
||||
<Hammer size={16} />
|
||||
<p className="text-sm">Open Builder</p>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<AgentFlowsList
|
||||
flows={agentFlows}
|
||||
selectedFlow={selectedFlow}
|
||||
handleClick={handleFlowClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ImportedSkillList
|
||||
skills={importedSkills}
|
||||
selectedSkill={selectedSkill}
|
||||
handleClick={setSelectedSkill}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Selected agent skill setting panel */}
|
||||
|
@ -349,7 +469,14 @@ export default function AdminAgents() {
|
|||
<div className="bg-theme-bg-secondary text-white rounded-xl flex-1 p-4">
|
||||
{SelectedSkillComponent ? (
|
||||
<>
|
||||
{selectedSkill.imported ? (
|
||||
{selectedFlow ? (
|
||||
<FlowPanel
|
||||
flow={selectedFlow}
|
||||
toggleFlow={toggleFlow}
|
||||
enabled={activeFlowIds.includes(selectedFlow.uuid)}
|
||||
onDelete={handleFlowDelete}
|
||||
/>
|
||||
) : selectedSkill.imported ? (
|
||||
<ImportedSkillConfig
|
||||
key={selectedSkill.hubId}
|
||||
selectedSkill={selectedSkill}
|
||||
|
@ -390,7 +517,7 @@ export default function AdminAgents() {
|
|||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-theme-text-secondary">
|
||||
<Robot size={40} />
|
||||
<p className="font-medium">Select an agent skill</p>
|
||||
<p className="font-medium">Select an agent skill or flow</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -15,13 +15,9 @@ export default function Main() {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserMenu>
|
||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
|
||||
{!isMobile && <Sidebar />}
|
||||
<DefaultChatContainer />
|
||||
</div>
|
||||
</UserMenu>
|
||||
</>
|
||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
|
||||
{!isMobile && <Sidebar />}
|
||||
<DefaultChatContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,19 +6,17 @@ import { useNavigate } from "react-router-dom";
|
|||
import Workspace from "@/models/workspace";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TITLE = "Create your first workspace";
|
||||
const DESCRIPTION =
|
||||
"Create your first workspace and get started with AnythingLLM.";
|
||||
|
||||
export default function CreateWorkspace({
|
||||
setHeader,
|
||||
setForwardBtn,
|
||||
setBackBtn,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const createWorkspaceRef = useRef();
|
||||
const { t } = useTranslation();
|
||||
const TITLE = t("onboarding.workspace.title");
|
||||
const DESCRIPTION = t("onboarding.workspace.description");
|
||||
|
||||
useEffect(() => {
|
||||
setHeader({ title: TITLE, description: DESCRIPTION });
|
||||
|
|
|
@ -40,10 +40,8 @@ import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
|
|||
import React, { useState, useEffect } from "react";
|
||||
import paths from "@/utils/paths";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TITLE = "Data Handling & Privacy";
|
||||
const DESCRIPTION =
|
||||
"We are committed to transparency and control when it comes to your personal data.";
|
||||
export const LLM_SELECTION_PRIVACY = {
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
|
@ -406,12 +404,16 @@ export const FALLBACKS = {
|
|||
};
|
||||
|
||||
export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
const { t } = useTranslation();
|
||||
const [llmChoice, setLLMChoice] = useState("openai");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [vectorDb, setVectorDb] = useState("pinecone");
|
||||
const [embeddingEngine, setEmbeddingEngine] = useState("openai");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const TITLE = t("onboarding.data.title");
|
||||
const DESCRIPTION = t("onboarding.data.description");
|
||||
|
||||
useEffect(() => {
|
||||
setHeader({ title: TITLE, description: DESCRIPTION });
|
||||
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
|
||||
|
@ -515,7 +517,7 @@ export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
</div>
|
||||
</div>
|
||||
<p className="text-theme-text-secondary text-sm font-medium py-1">
|
||||
These settings can be reconfigured at any time in the settings.
|
||||
{t("onboarding.data.settingsHint")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ import RGroupImgLight from "./r_group-light.png";
|
|||
import AnythingLLMLogo from "@/media/logo/anything-llm.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTheme } from "@/hooks/useTheme";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const IMG_SRCSET = {
|
||||
light: {
|
||||
|
@ -21,6 +22,7 @@ const IMG_SRCSET = {
|
|||
export default function OnboardingHome() {
|
||||
const navigate = useNavigate();
|
||||
const { theme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const srcSet = IMG_SRCSET?.[theme] || IMG_SRCSET.default;
|
||||
|
||||
return (
|
||||
|
@ -39,7 +41,7 @@ export default function OnboardingHome() {
|
|||
<div className="relative flex justify-center items-center m-auto">
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<p className="text-theme-text-primary font-thin text-[24px]">
|
||||
Welcome to
|
||||
{t("onboarding.home.title")}
|
||||
</p>
|
||||
<img
|
||||
src={AnythingLLMLogo}
|
||||
|
@ -50,7 +52,7 @@ export default function OnboardingHome() {
|
|||
onClick={() => navigate(paths.onboarding.llmPreference())}
|
||||
className="border-[2px] border-theme-text-primary animate-pulse light:animate-none w-full md:max-w-[350px] md:min-w-[300px] text-center py-3 bg-theme-button-primary hover:bg-theme-bg-secondary text-theme-text-primary font-semibold text-sm my-10 rounded-md "
|
||||
>
|
||||
Get started
|
||||
{t("onboarding.home.getStarted")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,6 @@ import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
|||
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||
import TogetherAILogo from "@/media/llmprovider/togetherai.png";
|
||||
import FireworksAILogo from "@/media/llmprovider/fireworksai.jpeg";
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
||||
import HuggingFaceLogo from "@/media/llmprovider/huggingface.png";
|
||||
import PerplexityLogo from "@/media/llmprovider/perplexity.png";
|
||||
|
@ -57,10 +56,7 @@ import System from "@/models/system";
|
|||
import paths from "@/utils/paths";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const TITLE = "LLM Preference";
|
||||
const DESCRIPTION =
|
||||
"AnythingLLM can work with many LLM providers. This will be the service which handles chatting.";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const LLMS = [
|
||||
{
|
||||
|
@ -253,6 +249,7 @@ export default function LLMPreference({
|
|||
setForwardBtn,
|
||||
setBackBtn,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [filteredLLMs, setFilteredLLMs] = useState([]);
|
||||
const [selectedLLM, setSelectedLLM] = useState(null);
|
||||
|
@ -262,6 +259,9 @@ export default function LLMPreference({
|
|||
const isHosted = window.location.hostname.includes("useanything.com");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const TITLE = t("onboarding.llm.title");
|
||||
const DESCRIPTION = t("onboarding.llm.description");
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
|
|
|
@ -6,9 +6,7 @@ import paths from "@/utils/paths";
|
|||
import { CheckCircle } from "@phosphor-icons/react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const TITLE = "Welcome to AnythingLLM";
|
||||
const DESCRIPTION = "Help us make AnythingLLM built for your needs. Optional.";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
async function sendQuestionnaire({ email, useCase, comment }) {
|
||||
if (import.meta.env.DEV) {
|
||||
|
@ -44,11 +42,15 @@ async function sendQuestionnaire({ email, useCase, comment }) {
|
|||
}
|
||||
|
||||
export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedOption, setSelectedOption] = useState("");
|
||||
const formRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const submitRef = useRef(null);
|
||||
|
||||
const TITLE = t("onboarding.survey.title");
|
||||
const DESCRIPTION = t("onboarding.survey.description");
|
||||
|
||||
function handleForward() {
|
||||
if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
|
||||
navigate(paths.onboarding.createWorkspace());
|
||||
|
@ -109,7 +111,9 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
<div className="w-full flex items-center justify-center px-1 md:px-8 py-4">
|
||||
<div className="w-auto flex flex-col gap-y-1 items-center">
|
||||
<CheckCircle size={60} className="text-green-500" />
|
||||
<p className="text-white text-lg">Thank you for your feedback!</p>
|
||||
<p className="text-white text-lg">
|
||||
{t("onboarding.survey.thankYou")}
|
||||
</p>
|
||||
<a
|
||||
href={paths.mailToMintplex()}
|
||||
className="text-sky-400 underline text-xs"
|
||||
|
@ -130,7 +134,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
htmlFor="email"
|
||||
className="text-theme-text-primary text-base font-medium"
|
||||
>
|
||||
What's your email?{" "}
|
||||
{t("onboarding.survey.email")}{" "}
|
||||
</label>
|
||||
<input
|
||||
name="email"
|
||||
|
@ -146,7 +150,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
className="text-theme-text-primary text-base font-medium"
|
||||
htmlFor="use_case"
|
||||
>
|
||||
What will you use AnythingLLM for?{" "}
|
||||
{t("onboarding.survey.useCase")}{" "}
|
||||
</label>
|
||||
<div className="mt-2 gap-y-3 flex flex-col">
|
||||
<label
|
||||
|
@ -172,7 +176,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
}`}
|
||||
></div>
|
||||
<div className="text-theme-text-primary text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
|
||||
For work
|
||||
{t("onboarding.survey.useCaseWork")}
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
|
@ -198,7 +202,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
}`}
|
||||
></div>
|
||||
<div className="text-theme-text-primary text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
|
||||
For my personal use
|
||||
{t("onboarding.survey.useCasePersonal")}
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
|
@ -224,7 +228,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
}`}
|
||||
></div>
|
||||
<div className="text-theme-text-primary text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
|
||||
Other
|
||||
{t("onboarding.survey.useCaseOther")}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -232,16 +236,16 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
|
||||
<div className="mt-8">
|
||||
<label htmlFor="comment" className="text-white text-base font-medium">
|
||||
Any comments for the team?{" "}
|
||||
{t("onboarding.survey.comment")}{" "}
|
||||
<span className="text-neutral-400 text-base font-light">
|
||||
(Optional)
|
||||
({t("common.optional")})
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
name="comment"
|
||||
rows={5}
|
||||
className="mt-2 bg-theme-settings-input-bg text-white text-sm rounded-lg focus:outline-primary-button active:outline-primary-button placeholder:text-theme-settings-input-placeholder outline-none block w-full p-2.5"
|
||||
placeholder="If you have any questions or comments right now, you can leave them here and we will get back to you. You can also email team@mintplexlabs.com"
|
||||
placeholder={t("onboarding.survey.commentPlaceholder")}
|
||||
wrap="soft"
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
@ -259,7 +263,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
onClick={skipSurvey}
|
||||
className="text-white text-base font-medium text-opacity-30 hover:text-opacity-100 mt-8"
|
||||
>
|
||||
Skip Survey
|
||||
{t("onboarding.survey.skip")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -5,11 +5,10 @@ import debounce from "lodash.debounce";
|
|||
import paths from "@/utils/paths";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
|
||||
|
||||
const TITLE = "User Setup";
|
||||
const DESCRIPTION = "Configure your user settings.";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedOption, setSelectedOption] = useState("");
|
||||
const [singleUserPasswordValid, setSingleUserPasswordValid] = useState(false);
|
||||
const [multiUserLoginValid, setMultiUserLoginValid] = useState(false);
|
||||
|
@ -18,6 +17,9 @@ export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
const justMeSubmitRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const TITLE = t("onboarding.userSetup.title");
|
||||
const DESCRIPTION = t("onboarding.userSetup.description");
|
||||
|
||||
function handleForward() {
|
||||
if (selectedOption === "just_me" && enablePassword) {
|
||||
justMeSubmitRef.current?.click();
|
||||
|
@ -56,7 +58,7 @@ export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
<div className="w-full flex items-center justify-center flex-col gap-y-6">
|
||||
<div className="flex flex-col border rounded-lg border-white/20 light:border-theme-sidebar-border p-8 items-center gap-y-4 w-full max-w-[600px]">
|
||||
<div className=" text-white text-sm font-semibold md:-ml-44">
|
||||
How many people will be using your instance?
|
||||
{t("onboarding.userSetup.howManyUsers")}
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-6 w-full justify-center">
|
||||
<button
|
||||
|
@ -67,7 +69,9 @@ export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
: "text-theme-text-primary border-theme-sidebar-border"
|
||||
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
|
||||
>
|
||||
<div className="text-center text-sm font-bold">Just me</div>
|
||||
<div className="text-center text-sm font-bold">
|
||||
{t("onboarding.userSetup.justMe")}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedOption("my_team")}
|
||||
|
@ -77,7 +81,9 @@ export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
|
|||
: "text-theme-text-primary border-theme-sidebar-border"
|
||||
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
|
||||
>
|
||||
<div className="text-center text-sm font-bold">My team</div>
|
||||
<div className="text-center text-sm font-bold">
|
||||
{t("onboarding.userSetup.myTeam")}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -108,6 +114,7 @@ const JustMe = ({
|
|||
justMeSubmitRef,
|
||||
navigate,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [itemSelected, setItemSelected] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const handleSubmit = async (e) => {
|
||||
|
@ -162,7 +169,7 @@ const JustMe = ({
|
|||
<div className="w-full flex items-center justify-center flex-col gap-y-6">
|
||||
<div className="flex flex-col border rounded-lg border-white/20 light:border-theme-sidebar-border p-8 items-center gap-y-4 w-full max-w-[600px]">
|
||||
<div className=" text-white text-sm font-semibold md:-ml-56">
|
||||
Would you like to set up a password?
|
||||
{t("onboarding.userSetup.setPassword")}
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-6 w-full justify-center">
|
||||
<button
|
||||
|
@ -173,7 +180,9 @@ const JustMe = ({
|
|||
: "text-theme-text-primary border-theme-sidebar-border"
|
||||
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
|
||||
>
|
||||
<div className="text-center text-sm font-bold">Yes</div>
|
||||
<div className="text-center text-sm font-bold">
|
||||
{t("common.yes")}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNo}
|
||||
|
@ -183,7 +192,9 @@ const JustMe = ({
|
|||
: "text-theme-text-primary border-theme-sidebar-border"
|
||||
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
|
||||
>
|
||||
<div className="text-center text-sm font-bold">No</div>
|
||||
<div className="text-center text-sm font-bold">
|
||||
{t("common.no")}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{enablePassword && (
|
||||
|
@ -192,7 +203,7 @@ const JustMe = ({
|
|||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
Instance Password
|
||||
{t("onboarding.userSetup.instancePassword")}
|
||||
</label>
|
||||
<input
|
||||
name="password"
|
||||
|
@ -205,12 +216,9 @@ const JustMe = ({
|
|||
onChange={handlePasswordChange}
|
||||
/>
|
||||
<div className="mt-4 text-white text-opacity-80 text-xs font-base -mb-2">
|
||||
Passwords must be at least 8 characters.
|
||||
{t("onboarding.userSetup.passwordReq")}
|
||||
<br />
|
||||
<i>
|
||||
It's important to save this password because there is no
|
||||
recovery method.
|
||||
</i>{" "}
|
||||
<i>{t("onboarding.userSetup.passwordWarn")}</i>{" "}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -226,6 +234,7 @@ const JustMe = ({
|
|||
};
|
||||
|
||||
const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
||||
const { t } = useTranslation();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
|
@ -275,7 +284,7 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
|||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
Admin account username
|
||||
{t("common.adminUsername")}
|
||||
</label>
|
||||
<input
|
||||
name="username"
|
||||
|
@ -289,16 +298,14 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
|||
/>
|
||||
</div>
|
||||
<p className=" text-white text-opacity-80 text-xs font-base">
|
||||
Username must be at least 6 characters long and only contain
|
||||
lowercase letters, numbers, underscores, and hyphens with no
|
||||
spaces.
|
||||
{t("onboarding.userSetup.adminUsernameReq")}
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
Admin account password
|
||||
{t("onboarding.userSetup.adminPassword")}
|
||||
</label>
|
||||
<input
|
||||
name="password"
|
||||
|
@ -312,16 +319,14 @@ const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
|
|||
/>
|
||||
</div>
|
||||
<p className=" text-white text-opacity-80 text-xs font-base">
|
||||
Password must be at least 8 characters long.
|
||||
{t("onboarding.userSetup.adminPasswordReq")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full justify-between items-center px-6 py-4 space-x-6 border-t rounded-b border-theme-sidebar-border">
|
||||
<div className="text-theme-text-secondary text-opacity-80 text-xs font-base">
|
||||
By default, you will be the only admin. Once onboarding is completed
|
||||
you can create and invite others to be users or admins. Do not lose
|
||||
your password as only admins can reset passwords.
|
||||
{t("onboarding.userSetup.teamHint")}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
|
|
@ -24,9 +24,9 @@ const markdown = markdownIt({
|
|||
<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 code</p>
|
||||
<button data-code-snippet data-code="code-${uuid}" class="flex items-center gap-x-1">
|
||||
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-3 w-3" 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 class="text-xs" style="margin: 0px;padding: 0px;">Copy block</p>
|
||||
</button>
|
||||
</div>
|
||||
<pre class="whitespace-pre-wrap">` +
|
||||
|
@ -40,9 +40,9 @@ const markdown = markdownIt({
|
|||
`<div class="whitespace-pre-line w-full hljs ${theme} light:border-solid light:border light:border-gray-700 rounded-lg px-4 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 code</p>
|
||||
<button data-code-snippet data-code="code-${uuid}" class="flex items-center gap-x-1">
|
||||
<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-3 w-3" 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 class="text-xs" style="margin: 0px;padding: 0px;">Copy block</p>
|
||||
</button>
|
||||
</div>
|
||||
<pre class="whitespace-pre-wrap">` +
|
||||
|
@ -55,6 +55,11 @@ const markdown = markdownIt({
|
|||
// Add custom renderer for strong tags to handle theme colors
|
||||
markdown.renderer.rules.strong_open = () => '<strong class="text-white">';
|
||||
markdown.renderer.rules.strong_close = () => "</strong>";
|
||||
markdown.renderer.rules.link_open = (tokens, idx) => {
|
||||
const token = tokens[idx];
|
||||
const href = token.attrs.find((attr) => attr[0] === "href");
|
||||
return `<a href="${href[1]}" target="_blank" rel="noopener noreferrer">`;
|
||||
};
|
||||
|
||||
// Custom renderer for responsive images rendered in markdown
|
||||
markdown.renderer.rules.image = function (tokens, idx) {
|
||||
|
|
|
@ -139,6 +139,14 @@ export default {
|
|||
return `/settings/beta-features`;
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
builder: () => {
|
||||
return `/settings/agents/builder`;
|
||||
},
|
||||
editAgent: (uuid) => {
|
||||
return `/settings/agents/builder/${uuid}`;
|
||||
},
|
||||
},
|
||||
communityHub: {
|
||||
website: () => {
|
||||
return import.meta.env.DEV
|
||||
|
|
|
@ -29,6 +29,7 @@ export default {
|
|||
"historical-msg-user": "#2C2F35",
|
||||
outline: "#4E5153",
|
||||
"primary-button": "var(--theme-button-primary)",
|
||||
"cta-button": "var(--theme-button-cta)",
|
||||
secondary: "#2C2F36",
|
||||
"dark-input": "#18181B",
|
||||
"mobile-onboarding": "#2C2F35",
|
||||
|
|
1
server/.gitignore
vendored
1
server/.gitignore
vendored
|
@ -9,6 +9,7 @@ storage/vector-cache/*.json
|
|||
storage/exports
|
||||
storage/imports
|
||||
storage/plugins/agent-skills/*
|
||||
storage/plugins/agent-flows/*
|
||||
!storage/documents/DOCUMENTS.md
|
||||
logs/server.log
|
||||
*.db
|
||||
|
|
202
server/endpoints/agentFlows.js
Normal file
202
server/endpoints/agentFlows.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
const { AgentFlows } = require("../utils/agentFlows");
|
||||
const {
|
||||
flexUserRoleValid,
|
||||
ROLES,
|
||||
} = require("../utils/middleware/multiUserProtected");
|
||||
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
||||
const { Telemetry } = require("../models/telemetry");
|
||||
|
||||
function agentFlowEndpoints(app) {
|
||||
if (!app) return;
|
||||
|
||||
// Save a flow configuration
|
||||
app.post(
|
||||
"/agent-flows/save",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { name, config, uuid } = request.body;
|
||||
|
||||
if (!name || !config) {
|
||||
return response.status(400).json({
|
||||
success: false,
|
||||
error: "Name and config are required",
|
||||
});
|
||||
}
|
||||
|
||||
const flow = AgentFlows.saveFlow(name, config, uuid);
|
||||
if (!flow) {
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to save flow",
|
||||
});
|
||||
}
|
||||
|
||||
if (!uuid) {
|
||||
await Telemetry.sendTelemetry("agent_flow_created", {
|
||||
blockCount: config.blocks?.length || 0,
|
||||
});
|
||||
}
|
||||
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
flow,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error saving flow:", error);
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// List all available flows
|
||||
app.get(
|
||||
"/agent-flows/list",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (_request, response) => {
|
||||
try {
|
||||
const flows = AgentFlows.listFlows();
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
flows,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error listing flows:", error);
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get a specific flow by UUID
|
||||
app.get(
|
||||
"/agent-flows/:uuid",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { uuid } = request.params;
|
||||
const flow = AgentFlows.loadFlow(uuid);
|
||||
if (!flow) {
|
||||
return response.status(404).json({
|
||||
success: false,
|
||||
error: "Flow not found",
|
||||
});
|
||||
}
|
||||
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
flow,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error getting flow:", error);
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Run a specific flow
|
||||
// app.post(
|
||||
// "/agent-flows/:uuid/run",
|
||||
// [validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
// async (request, response) => {
|
||||
// try {
|
||||
// const { uuid } = request.params;
|
||||
// const { variables = {} } = request.body;
|
||||
|
||||
// // TODO: Implement flow execution
|
||||
// console.log("Running flow with UUID:", uuid);
|
||||
|
||||
// await Telemetry.sendTelemetry("agent_flow_executed", {
|
||||
// variableCount: Object.keys(variables).length,
|
||||
// });
|
||||
|
||||
// return response.status(200).json({
|
||||
// success: true,
|
||||
// results: {
|
||||
// success: true,
|
||||
// results: "test",
|
||||
// variables: variables,
|
||||
// },
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.error("Error running flow:", error);
|
||||
// return response.status(500).json({
|
||||
// success: false,
|
||||
// error: error.message,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// Delete a specific flow
|
||||
app.delete(
|
||||
"/agent-flows/:uuid",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { uuid } = request.params;
|
||||
const { success } = AgentFlows.deleteFlow(uuid);
|
||||
|
||||
if (!success) {
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to delete flow",
|
||||
});
|
||||
}
|
||||
|
||||
return response.status(200).json({
|
||||
success,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting flow:", error);
|
||||
return response.status(500).json({
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Toggle flow active status
|
||||
app.post(
|
||||
"/agent-flows/:uuid/toggle",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { uuid } = request.params;
|
||||
const { active } = request.body;
|
||||
|
||||
const flow = AgentFlows.loadFlow(uuid);
|
||||
if (!flow) {
|
||||
return response
|
||||
.status(404)
|
||||
.json({ success: false, error: "Flow not found" });
|
||||
}
|
||||
|
||||
flow.config.active = active;
|
||||
const { success } = AgentFlows.saveFlow(flow.name, flow.config, uuid);
|
||||
|
||||
if (!success) {
|
||||
return response
|
||||
.status(500)
|
||||
.json({ success: false, error: "Failed to update flow" });
|
||||
}
|
||||
|
||||
return response.json({ success: true, flow });
|
||||
} catch (error) {
|
||||
console.error("Error toggling flow:", error);
|
||||
response.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { agentFlowEndpoints };
|
|
@ -26,6 +26,7 @@ const { agentWebsocket } = require("./endpoints/agentWebsocket");
|
|||
const { experimentalEndpoints } = require("./endpoints/experimental");
|
||||
const { browserExtensionEndpoints } = require("./endpoints/browserExtension");
|
||||
const { communityHubEndpoints } = require("./endpoints/communityHub");
|
||||
const { agentFlowEndpoints } = require("./endpoints/agentFlows");
|
||||
const app = express();
|
||||
const apiRouter = express.Router();
|
||||
const FILE_LIMIT = "3GB";
|
||||
|
@ -61,6 +62,7 @@ agentWebsocket(apiRouter);
|
|||
experimentalEndpoints(apiRouter);
|
||||
developerEndpoints(app, apiRouter);
|
||||
communityHubEndpoints(apiRouter);
|
||||
agentFlowEndpoints(apiRouter);
|
||||
|
||||
// Externally facing embedder endpoints
|
||||
embeddedEndpoints(apiRouter);
|
||||
|
|
|
@ -3,8 +3,9 @@ const {
|
|||
LLMPerformanceMonitor,
|
||||
} = require("../../helpers/chat/LLMPerformanceMonitor");
|
||||
const {
|
||||
handleDefaultStreamResponseV2,
|
||||
formatChatHistory,
|
||||
writeResponseChunk,
|
||||
clientAbortedHandler,
|
||||
} = require("../../helpers/chat/responses");
|
||||
const { toValidNumber } = require("../../http");
|
||||
|
||||
|
@ -142,6 +143,21 @@ class GenericOpenAiLLM {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and prepends reasoning from the response and returns the full text response.
|
||||
* @param {Object} response
|
||||
* @returns {string}
|
||||
*/
|
||||
#parseReasoningFromResponse({ message }) {
|
||||
let textResponse = message?.content;
|
||||
if (
|
||||
!!message?.reasoning_content &&
|
||||
message.reasoning_content.trim().length > 0
|
||||
)
|
||||
textResponse = `<think>${message.reasoning_content}</think>${textResponse}`;
|
||||
return textResponse;
|
||||
}
|
||||
|
||||
async getChatCompletion(messages = null, { temperature = 0.7 }) {
|
||||
const result = await LLMPerformanceMonitor.measureAsyncFunction(
|
||||
this.openai.chat.completions
|
||||
|
@ -163,7 +179,7 @@ class GenericOpenAiLLM {
|
|||
return null;
|
||||
|
||||
return {
|
||||
textResponse: result.output.choices[0].message.content,
|
||||
textResponse: this.#parseReasoningFromResponse(result.output.choices[0]),
|
||||
metrics: {
|
||||
prompt_tokens: result.output?.usage?.prompt_tokens || 0,
|
||||
completion_tokens: result.output?.usage?.completion_tokens || 0,
|
||||
|
@ -191,8 +207,141 @@ class GenericOpenAiLLM {
|
|||
return measuredStreamRequest;
|
||||
}
|
||||
|
||||
// TODO: This is a copy of the generic handleStream function in responses.js
|
||||
// to specifically handle the DeepSeek reasoning model `reasoning_content` field.
|
||||
// When or if ever possible, we should refactor this to be in the generic function.
|
||||
handleStream(response, stream, responseProps) {
|
||||
return handleDefaultStreamResponseV2(response, stream, responseProps);
|
||||
const { uuid = uuidv4(), sources = [] } = responseProps;
|
||||
let hasUsageMetrics = false;
|
||||
let usage = {
|
||||
completion_tokens: 0,
|
||||
};
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
let fullText = "";
|
||||
let reasoningText = "";
|
||||
|
||||
// Establish listener to early-abort a streaming response
|
||||
// in case things go sideways or the user does not like the response.
|
||||
// We preserve the generated text but continue as if chat was completed
|
||||
// to preserve previously generated content.
|
||||
const handleAbort = () => {
|
||||
stream?.endMeasurement(usage);
|
||||
clientAbortedHandler(resolve, fullText);
|
||||
};
|
||||
response.on("close", handleAbort);
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
const message = chunk?.choices?.[0];
|
||||
const token = message?.delta?.content;
|
||||
const reasoningToken = message?.delta?.reasoning_content;
|
||||
|
||||
if (
|
||||
chunk.hasOwnProperty("usage") && // exists
|
||||
!!chunk.usage && // is not null
|
||||
Object.values(chunk.usage).length > 0 // has values
|
||||
) {
|
||||
if (chunk.usage.hasOwnProperty("prompt_tokens")) {
|
||||
usage.prompt_tokens = Number(chunk.usage.prompt_tokens);
|
||||
}
|
||||
|
||||
if (chunk.usage.hasOwnProperty("completion_tokens")) {
|
||||
hasUsageMetrics = true; // to stop estimating counter
|
||||
usage.completion_tokens = Number(chunk.usage.completion_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
// Reasoning models will always return the reasoning text before the token text.
|
||||
if (reasoningToken) {
|
||||
// If the reasoning text is empty (''), we need to initialize it
|
||||
// and send the first chunk of reasoning text.
|
||||
if (reasoningText.length === 0) {
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
type: "textResponseChunk",
|
||||
textResponse: `<think>${reasoningToken}`,
|
||||
close: false,
|
||||
error: false,
|
||||
});
|
||||
reasoningText += `<think>${reasoningToken}`;
|
||||
continue;
|
||||
} else {
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
type: "textResponseChunk",
|
||||
textResponse: reasoningToken,
|
||||
close: false,
|
||||
error: false,
|
||||
});
|
||||
reasoningText += reasoningToken;
|
||||
}
|
||||
}
|
||||
|
||||
// If the reasoning text is not empty, but the reasoning token is empty
|
||||
// and the token text is not empty we need to close the reasoning text and begin sending the token text.
|
||||
if (!!reasoningText && !reasoningToken && token) {
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
type: "textResponseChunk",
|
||||
textResponse: `</think>`,
|
||||
close: false,
|
||||
error: false,
|
||||
});
|
||||
fullText += `${reasoningText}</think>`;
|
||||
reasoningText = "";
|
||||
}
|
||||
|
||||
if (token) {
|
||||
fullText += token;
|
||||
// If we never saw a usage metric, we can estimate them by number of completion chunks
|
||||
if (!hasUsageMetrics) usage.completion_tokens++;
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources: [],
|
||||
type: "textResponseChunk",
|
||||
textResponse: token,
|
||||
close: false,
|
||||
error: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
message?.hasOwnProperty("finish_reason") && // Got valid message and it is an object with finish_reason
|
||||
message.finish_reason !== "" &&
|
||||
message.finish_reason !== null
|
||||
) {
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
sources,
|
||||
type: "textResponseChunk",
|
||||
textResponse: "",
|
||||
close: true,
|
||||
error: false,
|
||||
});
|
||||
response.removeListener("close", handleAbort);
|
||||
stream?.endMeasurement(usage);
|
||||
resolve(fullText);
|
||||
break; // Break streaming when a valid finish_reason is first encountered
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`\x1b[43m\x1b[34m[STREAMING ERROR]\x1b[0m ${e.message}`);
|
||||
writeResponseChunk(response, {
|
||||
uuid,
|
||||
type: "abort",
|
||||
textResponse: null,
|
||||
sources: [],
|
||||
close: true,
|
||||
error: e.message,
|
||||
});
|
||||
stream?.endMeasurement(usage);
|
||||
resolve(fullText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Simple wrapper for dynamic embedder & normalize interface for all LLM implementations
|
||||
|
|
146
server/utils/agentFlows/executor.js
Normal file
146
server/utils/agentFlows/executor.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
const { FLOW_TYPES } = require("./flowTypes");
|
||||
const executeApiCall = require("./executors/api-call");
|
||||
const executeWebsite = require("./executors/website");
|
||||
const executeFile = require("./executors/file");
|
||||
const executeCode = require("./executors/code");
|
||||
const executeLLMInstruction = require("./executors/llm-instruction");
|
||||
const executeWebScraping = require("./executors/web-scraping");
|
||||
const { Telemetry } = require("../../models/telemetry");
|
||||
|
||||
class FlowExecutor {
|
||||
constructor() {
|
||||
this.variables = {};
|
||||
this.introspect = () => {}; // Default no-op introspect
|
||||
this.logger = console.info; // Default console.info
|
||||
}
|
||||
|
||||
attachLogging(introspectFn, loggerFn) {
|
||||
this.introspect = introspectFn || (() => {});
|
||||
this.logger = loggerFn || console.info;
|
||||
}
|
||||
|
||||
// Utility to replace variables in config
|
||||
replaceVariables(config) {
|
||||
const deepReplace = (obj) => {
|
||||
if (typeof obj === "string") {
|
||||
return obj.replace(/\${([^}]+)}/g, (match, varName) => {
|
||||
return this.variables[varName] !== undefined
|
||||
? this.variables[varName]
|
||||
: match;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => deepReplace(item));
|
||||
}
|
||||
if (obj && typeof obj === "object") {
|
||||
const result = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
result[key] = deepReplace(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
return deepReplace(config);
|
||||
}
|
||||
|
||||
// Main execution method
|
||||
async executeStep(step) {
|
||||
const config = this.replaceVariables(step.config);
|
||||
let result;
|
||||
// Create execution context with introspect
|
||||
const context = {
|
||||
introspect: this.introspect,
|
||||
variables: this.variables,
|
||||
logger: this.logger,
|
||||
model: process.env.LLM_PROVIDER_MODEL || "gpt-4",
|
||||
provider: process.env.LLM_PROVIDER || "openai",
|
||||
};
|
||||
|
||||
switch (step.type) {
|
||||
case FLOW_TYPES.START.type:
|
||||
// For start blocks, we just initialize variables if they're not already set
|
||||
if (config.variables) {
|
||||
config.variables.forEach((v) => {
|
||||
if (v.name && !this.variables[v.name]) {
|
||||
this.variables[v.name] = v.value || "";
|
||||
}
|
||||
});
|
||||
}
|
||||
result = this.variables;
|
||||
break;
|
||||
case FLOW_TYPES.API_CALL.type:
|
||||
result = await executeApiCall(config, context);
|
||||
break;
|
||||
case FLOW_TYPES.WEBSITE.type:
|
||||
result = await executeWebsite(config, context);
|
||||
break;
|
||||
case FLOW_TYPES.FILE.type:
|
||||
result = await executeFile(config, context);
|
||||
break;
|
||||
case FLOW_TYPES.CODE.type:
|
||||
result = await executeCode(config, context);
|
||||
break;
|
||||
case FLOW_TYPES.LLM_INSTRUCTION.type:
|
||||
result = await executeLLMInstruction(config, context);
|
||||
break;
|
||||
case FLOW_TYPES.WEB_SCRAPING.type:
|
||||
result = await executeWebScraping(config, context);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown flow type: ${step.type}`);
|
||||
}
|
||||
|
||||
// Store result in variable if specified
|
||||
if (config.resultVariable || config.responseVariable) {
|
||||
const varName = config.resultVariable || config.responseVariable;
|
||||
this.variables[varName] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Execute entire flow
|
||||
async executeFlow(
|
||||
flow,
|
||||
initialVariables = {},
|
||||
introspectFn = null,
|
||||
loggerFn = null
|
||||
) {
|
||||
await Telemetry.sendTelemetry("agent_flow_execution_started");
|
||||
|
||||
// Initialize variables with both initial values and any passed-in values
|
||||
this.variables = {
|
||||
...(
|
||||
flow.config.steps.find((s) => s.type === "start")?.config?.variables ||
|
||||
[]
|
||||
).reduce((acc, v) => ({ ...acc, [v.name]: v.value }), {}),
|
||||
...initialVariables, // This will override any default values with passed-in values
|
||||
};
|
||||
|
||||
this.attachLogging(introspectFn, loggerFn);
|
||||
const results = [];
|
||||
|
||||
for (const step of flow.config.steps) {
|
||||
try {
|
||||
const result = await this.executeStep(step);
|
||||
results.push({ success: true, result });
|
||||
} catch (error) {
|
||||
results.push({ success: false, error: error.message });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: results.every((r) => r.success),
|
||||
results,
|
||||
variables: this.variables,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FlowExecutor,
|
||||
FLOW_TYPES,
|
||||
};
|
60
server/utils/agentFlows/executors/api-call.js
Normal file
60
server/utils/agentFlows/executors/api-call.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
const { safeJsonParse } = require("../../http");
|
||||
|
||||
/**
|
||||
* Execute an API call flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @param {Object} context Execution context with introspect function
|
||||
* @returns {Promise<string>} Response data
|
||||
*/
|
||||
async function executeApiCall(config, context) {
|
||||
const { url, method, headers = [], body, bodyType, formData } = config;
|
||||
const { introspect } = context;
|
||||
|
||||
introspect(`Making ${method} request to external API...`);
|
||||
|
||||
const requestConfig = {
|
||||
method,
|
||||
headers: headers.reduce((acc, h) => ({ ...acc, [h.key]: h.value }), {}),
|
||||
};
|
||||
|
||||
if (["POST", "PUT", "PATCH"].includes(method)) {
|
||||
if (bodyType === "form") {
|
||||
const formDataObj = new URLSearchParams();
|
||||
formData.forEach(({ key, value }) => formDataObj.append(key, value));
|
||||
requestConfig.body = formDataObj.toString();
|
||||
requestConfig.headers["Content-Type"] =
|
||||
"application/x-www-form-urlencoded";
|
||||
} else if (bodyType === "json") {
|
||||
const parsedBody = safeJsonParse(body, null);
|
||||
if (parsedBody !== null) {
|
||||
requestConfig.body = JSON.stringify(parsedBody);
|
||||
}
|
||||
requestConfig.headers["Content-Type"] = "application/json";
|
||||
} else if (bodyType === "text") {
|
||||
requestConfig.body = String(body);
|
||||
} else {
|
||||
requestConfig.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
introspect(`Sending body to ${url}: ${requestConfig?.body || "No body"}`);
|
||||
const response = await fetch(url, requestConfig);
|
||||
if (!response.ok) {
|
||||
introspect(`Request failed with status ${response.status}`);
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
introspect(`API call completed`);
|
||||
return await response
|
||||
.text()
|
||||
.then((text) =>
|
||||
safeJsonParse(text, "Failed to parse output from API call block")
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`API Call failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = executeApiCall;
|
12
server/utils/agentFlows/executors/code.js
Normal file
12
server/utils/agentFlows/executors/code.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Execute a code flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @returns {Promise<Object>} Result of the code execution
|
||||
*/
|
||||
async function executeCode(config) {
|
||||
// For now just log what would happen
|
||||
console.log("Code execution:", config);
|
||||
return { success: true, message: "Code executed (placeholder)" };
|
||||
}
|
||||
|
||||
module.exports = executeCode;
|
12
server/utils/agentFlows/executors/file.js
Normal file
12
server/utils/agentFlows/executors/file.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Execute a file operation flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @returns {Promise<Object>} Result of the file operation
|
||||
*/
|
||||
async function executeFile(config) {
|
||||
// For now just log what would happen
|
||||
console.log("File operation:", config);
|
||||
return { success: true, message: "File operation executed (placeholder)" };
|
||||
}
|
||||
|
||||
module.exports = executeFile;
|
49
server/utils/agentFlows/executors/llm-instruction.js
Normal file
49
server/utils/agentFlows/executors/llm-instruction.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
const AIbitat = require("../../agents/aibitat");
|
||||
|
||||
/**
|
||||
* Execute an LLM instruction flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @param {{introspect: Function, variables: Object, logger: Function}} context Execution context with introspect function
|
||||
* @returns {Promise<string>} Processed result
|
||||
*/
|
||||
async function executeLLMInstruction(config, context) {
|
||||
const { instruction, inputVariable, resultVariable } = config;
|
||||
const { introspect, variables, logger } = context;
|
||||
|
||||
introspect(`Processing data with LLM instruction...`);
|
||||
if (!variables[inputVariable]) {
|
||||
logger(`Input variable ${inputVariable} not found`);
|
||||
throw new Error(`Input variable ${inputVariable} not found`);
|
||||
}
|
||||
|
||||
try {
|
||||
introspect(`Sending request to LLM...`);
|
||||
|
||||
// Ensure the input is a string since we are sending it to the LLM direct as a message
|
||||
let input = variables[inputVariable];
|
||||
if (typeof input === "object") input = JSON.stringify(input);
|
||||
if (typeof input !== "string") input = String(input);
|
||||
|
||||
const aibitat = new AIbitat();
|
||||
const provider = aibitat.getProviderForConfig(aibitat.defaultProvider);
|
||||
const completion = await provider.complete([
|
||||
{
|
||||
role: "system",
|
||||
content: `Follow these instructions carefully: ${instruction}`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: input,
|
||||
},
|
||||
]);
|
||||
|
||||
introspect(`Successfully received LLM response`);
|
||||
if (resultVariable) config.resultVariable = resultVariable;
|
||||
return completion.result;
|
||||
} catch (error) {
|
||||
logger(`LLM processing failed: ${error.message}`, error);
|
||||
throw new Error(`LLM processing failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = executeLLMInstruction;
|
55
server/utils/agentFlows/executors/web-scraping.js
Normal file
55
server/utils/agentFlows/executors/web-scraping.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
const { CollectorApi } = require("../../collectorApi");
|
||||
const { TokenManager } = require("../../helpers/tiktoken");
|
||||
const Provider = require("../../agents/aibitat/providers/ai-provider");
|
||||
const { summarizeContent } = require("../../agents/aibitat/utils/summarize");
|
||||
|
||||
/**
|
||||
* Execute a web scraping flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @param {Object} context Execution context with introspect function
|
||||
* @returns {Promise<string>} Scraped content
|
||||
*/
|
||||
async function executeWebScraping(config, context) {
|
||||
const { url } = config;
|
||||
const { introspect, model, provider } = context;
|
||||
|
||||
if (!url) {
|
||||
throw new Error("URL is required for web scraping");
|
||||
}
|
||||
|
||||
introspect(`Scraping the content of ${url}`);
|
||||
const { success, content } = await new CollectorApi().getLinkContent(url);
|
||||
|
||||
if (!success) {
|
||||
introspect(`Could not scrape ${url}. Cannot use this page's content.`);
|
||||
throw new Error("URL could not be scraped and no content was found.");
|
||||
}
|
||||
|
||||
introspect(`Successfully scraped content from ${url}`);
|
||||
|
||||
if (!content || content?.length === 0) {
|
||||
throw new Error("There was no content to be collected or read.");
|
||||
}
|
||||
|
||||
const tokenCount = new TokenManager(model).countFromString(content);
|
||||
const contextLimit = Provider.contextLimit(provider, model);
|
||||
|
||||
if (tokenCount < contextLimit) {
|
||||
return content;
|
||||
}
|
||||
|
||||
introspect(
|
||||
`This page's content is way too long. I will summarize it right now.`
|
||||
);
|
||||
const summary = await summarizeContent({
|
||||
provider,
|
||||
model,
|
||||
content,
|
||||
});
|
||||
|
||||
introspect(`Successfully summarized content`);
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
module.exports = executeWebScraping;
|
12
server/utils/agentFlows/executors/website.js
Normal file
12
server/utils/agentFlows/executors/website.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Execute a website interaction flow step
|
||||
* @param {Object} config Flow step configuration
|
||||
* @returns {Promise<Object>} Result of the website interaction
|
||||
*/
|
||||
async function executeWebsite(config) {
|
||||
// For now just log what would happen
|
||||
console.log("Website action:", config);
|
||||
return { success: true, message: "Website action executed (placeholder)" };
|
||||
}
|
||||
|
||||
module.exports = executeWebsite;
|
133
server/utils/agentFlows/flowTypes.js
Normal file
133
server/utils/agentFlows/flowTypes.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
const FLOW_TYPES = {
|
||||
START: {
|
||||
type: "start",
|
||||
description: "Initialize flow variables",
|
||||
parameters: {
|
||||
variables: {
|
||||
type: "array",
|
||||
description: "List of variables to initialize",
|
||||
},
|
||||
},
|
||||
},
|
||||
API_CALL: {
|
||||
type: "apiCall",
|
||||
description: "Make an HTTP request to an API endpoint",
|
||||
parameters: {
|
||||
url: { type: "string", description: "The URL to make the request to" },
|
||||
method: { type: "string", description: "HTTP method (GET, POST, etc.)" },
|
||||
headers: {
|
||||
type: "array",
|
||||
description: "Request headers as key-value pairs",
|
||||
},
|
||||
bodyType: {
|
||||
type: "string",
|
||||
description: "Type of request body (json, form)",
|
||||
},
|
||||
body: {
|
||||
type: "string",
|
||||
description:
|
||||
"Request body content. If body type is json, always return a valid json object. If body type is form, always return a valid form data object.",
|
||||
},
|
||||
formData: { type: "array", description: "Form data as key-value pairs" },
|
||||
responseVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the response",
|
||||
},
|
||||
},
|
||||
examples: [
|
||||
{
|
||||
url: "https://api.example.com/data",
|
||||
method: "GET",
|
||||
headers: [{ key: "Authorization", value: "Bearer 1234567890" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
WEBSITE: {
|
||||
type: "website",
|
||||
description: "Interact with a website",
|
||||
parameters: {
|
||||
url: { type: "string", description: "The URL of the website" },
|
||||
selector: {
|
||||
type: "string",
|
||||
description: "CSS selector for targeting elements",
|
||||
},
|
||||
action: {
|
||||
type: "string",
|
||||
description: "Action to perform (read, click, type)",
|
||||
},
|
||||
value: { type: "string", description: "Value to use for type action" },
|
||||
resultVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the result",
|
||||
},
|
||||
},
|
||||
},
|
||||
FILE: {
|
||||
type: "file",
|
||||
description: "Perform file system operations",
|
||||
parameters: {
|
||||
path: { type: "string", description: "Path to the file" },
|
||||
operation: {
|
||||
type: "string",
|
||||
description: "Operation to perform (read, write, append)",
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
description: "Content for write/append operations",
|
||||
},
|
||||
resultVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the result",
|
||||
},
|
||||
},
|
||||
},
|
||||
CODE: {
|
||||
type: "code",
|
||||
description: "Execute code in various languages",
|
||||
parameters: {
|
||||
language: {
|
||||
type: "string",
|
||||
description: "Programming language to execute",
|
||||
},
|
||||
code: { type: "string", description: "Code to execute" },
|
||||
resultVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the result",
|
||||
},
|
||||
},
|
||||
},
|
||||
LLM_INSTRUCTION: {
|
||||
type: "llmInstruction",
|
||||
description: "Process data using LLM instructions",
|
||||
parameters: {
|
||||
instruction: {
|
||||
type: "string",
|
||||
description: "The instruction for the LLM to follow",
|
||||
},
|
||||
inputVariable: {
|
||||
type: "string",
|
||||
description: "Variable containing the input data to process",
|
||||
},
|
||||
resultVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the processed result",
|
||||
},
|
||||
},
|
||||
},
|
||||
WEB_SCRAPING: {
|
||||
type: "webScraping",
|
||||
description: "Scrape content from a webpage",
|
||||
parameters: {
|
||||
url: {
|
||||
type: "string",
|
||||
description: "The URL of the webpage to scrape",
|
||||
},
|
||||
resultVariable: {
|
||||
type: "string",
|
||||
description: "Variable to store the scraped content",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports.FLOW_TYPES = FLOW_TYPES;
|
238
server/utils/agentFlows/index.js
Normal file
238
server/utils/agentFlows/index.js
Normal file
|
@ -0,0 +1,238 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { FlowExecutor } = require("./executor");
|
||||
const { normalizePath } = require("../files");
|
||||
const { safeJsonParse } = require("../http");
|
||||
|
||||
class AgentFlows {
|
||||
static flowsDir = process.env.STORAGE_DIR
|
||||
? path.join(process.env.STORAGE_DIR, "plugins", "agent-flows")
|
||||
: path.join(process.cwd(), "storage", "plugins", "agent-flows");
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Ensure flows directory exists
|
||||
* @returns {Boolean} True if directory exists, false otherwise
|
||||
*/
|
||||
static createOrCheckFlowsDir() {
|
||||
try {
|
||||
if (fs.existsSync(AgentFlows.flowsDir)) return true;
|
||||
fs.mkdirSync(AgentFlows.flowsDir, { recursive: true });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to create flows directory:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get all flow files with their contents
|
||||
* @returns {Object} Map of flow UUID to flow config
|
||||
*/
|
||||
static getAllFlows() {
|
||||
AgentFlows.createOrCheckFlowsDir();
|
||||
const files = fs.readdirSync(AgentFlows.flowsDir);
|
||||
const flows = {};
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".json")) continue;
|
||||
try {
|
||||
const filePath = path.join(AgentFlows.flowsDir, file);
|
||||
const content = fs.readFileSync(normalizePath(filePath), "utf8");
|
||||
const config = JSON.parse(content);
|
||||
const id = file.replace(".json", "");
|
||||
flows[id] = config;
|
||||
} catch (error) {
|
||||
console.error(`Error reading flow file ${file}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return flows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a flow configuration by UUID
|
||||
* @param {string} uuid - The UUID of the flow to load
|
||||
* @returns {Object|null} Flow configuration or null if not found
|
||||
*/
|
||||
static loadFlow(uuid) {
|
||||
try {
|
||||
const flowJsonPath = normalizePath(
|
||||
path.join(AgentFlows.flowsDir, `${uuid}.json`)
|
||||
);
|
||||
if (!uuid || !fs.existsSync(flowJsonPath)) return null;
|
||||
const flow = safeJsonParse(fs.readFileSync(flowJsonPath, "utf8"), null);
|
||||
if (!flow) return null;
|
||||
|
||||
return {
|
||||
name: flow.name,
|
||||
uuid,
|
||||
config: flow,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to load flow:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a flow configuration
|
||||
* @param {string} name - The name of the flow
|
||||
* @param {Object} config - The flow configuration
|
||||
* @param {string|null} uuid - Optional UUID for the flow
|
||||
* @returns {Object} Result of the save operation
|
||||
*/
|
||||
static saveFlow(name, config, uuid = null) {
|
||||
try {
|
||||
AgentFlows.createOrCheckFlowsDir();
|
||||
|
||||
if (!uuid) uuid = uuidv4();
|
||||
const normalizedUuid = normalizePath(`${uuid}.json`);
|
||||
const filePath = path.join(AgentFlows.flowsDir, normalizedUuid);
|
||||
fs.writeFileSync(filePath, JSON.stringify({ ...config, name }, null, 2));
|
||||
return { success: true, uuid };
|
||||
} catch (error) {
|
||||
console.error("Failed to save flow:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available flows
|
||||
* @returns {Array} Array of flow summaries
|
||||
*/
|
||||
static listFlows() {
|
||||
try {
|
||||
const flows = AgentFlows.getAllFlows();
|
||||
return Object.entries(flows).map(([uuid, flow]) => ({
|
||||
name: flow.name,
|
||||
uuid,
|
||||
description: flow.description,
|
||||
active: flow.active !== false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Failed to list flows:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a flow by UUID
|
||||
* @param {string} uuid - The UUID of the flow to delete
|
||||
* @returns {Object} Result of the delete operation
|
||||
*/
|
||||
static deleteFlow(uuid) {
|
||||
try {
|
||||
const filePath = normalizePath(
|
||||
path.join(AgentFlows.flowsDir, `${uuid}.json`)
|
||||
);
|
||||
if (!fs.existsSync(filePath)) throw new Error(`Flow ${uuid} not found`);
|
||||
fs.rmSync(filePath);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Failed to delete flow:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a flow by UUID
|
||||
* @param {string} uuid - The UUID of the flow to execute
|
||||
* @param {Object} variables - Initial variables for the flow
|
||||
* @param {Function} introspectFn - Function to introspect the flow
|
||||
* @param {Function} loggerFn - Function to log the flow
|
||||
* @returns {Promise<Object>} Result of flow execution
|
||||
*/
|
||||
static async executeFlow(
|
||||
uuid,
|
||||
variables = {},
|
||||
introspectFn = null,
|
||||
loggerFn = null
|
||||
) {
|
||||
const flow = AgentFlows.loadFlow(uuid);
|
||||
if (!flow) throw new Error(`Flow ${uuid} not found`);
|
||||
const flowExecutor = new FlowExecutor();
|
||||
return await flowExecutor.executeFlow(
|
||||
flow,
|
||||
variables,
|
||||
introspectFn,
|
||||
loggerFn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active flows as plugins that can be loaded into the agent
|
||||
* @returns {string[]} Array of flow names in @@flow_{uuid} format
|
||||
*/
|
||||
static activeFlowPlugins() {
|
||||
const flows = AgentFlows.getAllFlows();
|
||||
return Object.entries(flows)
|
||||
.filter(([_, flow]) => flow.active !== false)
|
||||
.map(([uuid]) => `@@flow_${uuid}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a flow plugin by its UUID
|
||||
* @param {string} uuid - The UUID of the flow to load
|
||||
* @returns {Object|null} Plugin configuration or null if not found
|
||||
*/
|
||||
static loadFlowPlugin(uuid) {
|
||||
const flow = AgentFlows.loadFlow(uuid);
|
||||
if (!flow) return null;
|
||||
|
||||
const startBlock = flow.config.steps?.find((s) => s.type === "start");
|
||||
const variables = startBlock?.config?.variables || [];
|
||||
|
||||
return {
|
||||
name: `flow_${uuid}`,
|
||||
description: `Execute agent flow: ${flow.name}`,
|
||||
plugin: (_runtimeArgs = {}) => ({
|
||||
name: `flow_${uuid}`,
|
||||
description: flow.description || `Execute agent flow: ${flow.name}`,
|
||||
setup: (aibitat) => {
|
||||
aibitat.function({
|
||||
name: `flow_${uuid}`,
|
||||
description: flow.description || `Execute agent flow: ${flow.name}`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: variables.reduce((acc, v) => {
|
||||
if (v.name) {
|
||||
acc[v.name] = {
|
||||
type: "string",
|
||||
description:
|
||||
v.description || `Value for variable ${v.name}`,
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
},
|
||||
handler: async (args) => {
|
||||
aibitat.introspect(`Executing flow: ${flow.name}`);
|
||||
const result = await AgentFlows.executeFlow(
|
||||
uuid,
|
||||
args,
|
||||
aibitat.introspect,
|
||||
aibitat.handlerProps.log
|
||||
);
|
||||
if (!result.success) {
|
||||
aibitat.introspect(
|
||||
`Flow failed: ${result.results[0]?.error || "Unknown error"}`
|
||||
);
|
||||
return `Flow execution failed: ${result.results[0]?.error || "Unknown error"}`;
|
||||
}
|
||||
aibitat.introspect(`${flow.name} completed successfully`);
|
||||
return typeof result === "object"
|
||||
? JSON.stringify(result)
|
||||
: String(result);
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
flowName: flow.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.AgentFlows = AgentFlows;
|
|
@ -3,6 +3,7 @@ const { SystemSettings } = require("../../models/systemSettings");
|
|||
const { safeJsonParse } = require("../http");
|
||||
const Provider = require("./aibitat/providers/ai-provider");
|
||||
const ImportedPlugin = require("./imported");
|
||||
const { AgentFlows } = require("../agentFlows");
|
||||
|
||||
// This is a list of skills that are built-in and default enabled.
|
||||
const DEFAULT_SKILLS = [
|
||||
|
@ -28,7 +29,8 @@ const WORKSPACE_AGENT = {
|
|||
role: Provider.systemPrompt(provider),
|
||||
functions: [
|
||||
...(await agentSkillsFromSystemSettings()),
|
||||
...(await ImportedPlugin.activeImportedPlugins()),
|
||||
...ImportedPlugin.activeImportedPlugins(),
|
||||
...AgentFlows.activeFlowPlugins(),
|
||||
],
|
||||
};
|
||||
},
|
||||
|
|
|
@ -59,9 +59,9 @@ class ImportedPlugin {
|
|||
/**
|
||||
* Loads plugins from `plugins` folder in storage that are custom loaded and defined.
|
||||
* only loads plugins that are active: true.
|
||||
* @returns {Promise<string[]>} - array of plugin names to be loaded later.
|
||||
* @returns {string[]} - array of plugin names to be loaded later.
|
||||
*/
|
||||
static async activeImportedPlugins() {
|
||||
static activeImportedPlugins() {
|
||||
const plugins = [];
|
||||
this.checkPluginFolderExists();
|
||||
const folders = fs.readdirSync(path.resolve(pluginsPath));
|
||||
|
|
|
@ -7,6 +7,7 @@ const { WorkspaceChats } = require("../../models/workspaceChats");
|
|||
const { safeJsonParse } = require("../http");
|
||||
const { USER_AGENT, WORKSPACE_AGENT } = require("./defaults");
|
||||
const ImportedPlugin = require("./imported");
|
||||
const { AgentFlows } = require("../agentFlows");
|
||||
|
||||
class AgentHandler {
|
||||
#invocationUUID;
|
||||
|
@ -337,26 +338,27 @@ class AgentHandler {
|
|||
for (const [param, definition] of Object.entries(config)) {
|
||||
if (
|
||||
definition.required &&
|
||||
(!args.hasOwnProperty(param) || args[param] === null)
|
||||
(!Object.prototype.hasOwnProperty.call(args, param) ||
|
||||
args[param] === null)
|
||||
) {
|
||||
this.log(
|
||||
`'${param}' required parameter for '${pluginName}' plugin is missing. Plugin may not function or crash agent.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
callOpts[param] = args.hasOwnProperty(param)
|
||||
callOpts[param] = Object.prototype.hasOwnProperty.call(args, param)
|
||||
? args[param]
|
||||
: definition.default || null;
|
||||
}
|
||||
return callOpts;
|
||||
}
|
||||
|
||||
#attachPlugins(args) {
|
||||
async #attachPlugins(args) {
|
||||
for (const name of this.#funcsToLoad) {
|
||||
// Load child plugin
|
||||
if (name.includes("#")) {
|
||||
const [parent, childPluginName] = name.split("#");
|
||||
if (!AgentPlugins.hasOwnProperty(parent)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(AgentPlugins, parent)) {
|
||||
this.log(
|
||||
`${parent} is not a valid plugin. Skipping inclusion to agent cluster.`
|
||||
);
|
||||
|
@ -385,6 +387,24 @@ class AgentHandler {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Load flow plugin. This is marked by `@@flow_` in the array of functions to load.
|
||||
if (name.startsWith("@@flow_")) {
|
||||
const uuid = name.replace("@@flow_", "");
|
||||
const plugin = AgentFlows.loadFlowPlugin(uuid);
|
||||
if (!plugin) {
|
||||
this.log(
|
||||
`Flow ${uuid} not found in flows directory. Skipping inclusion to agent cluster.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.aibitat.use(plugin.plugin());
|
||||
this.log(
|
||||
`Attached flow ${plugin.name} (${plugin.flowName}) plugin to Agent cluster`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load imported plugin. This is marked by `@@` in the array of functions to load.
|
||||
// and is the @@hubID of the plugin.
|
||||
if (name.startsWith("@@")) {
|
||||
|
@ -407,7 +427,7 @@ class AgentHandler {
|
|||
}
|
||||
|
||||
// Load single-stage plugin.
|
||||
if (!AgentPlugins.hasOwnProperty(name)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(AgentPlugins, name)) {
|
||||
this.log(
|
||||
`${name} is not a valid plugin. Skipping inclusion to agent cluster.`
|
||||
);
|
||||
|
@ -480,7 +500,7 @@ class AgentHandler {
|
|||
await this.#loadAgents();
|
||||
|
||||
// Attach all required plugins for functions to operate.
|
||||
this.#attachPlugins(args);
|
||||
await this.#attachPlugins(args);
|
||||
}
|
||||
|
||||
startAgentCluster() {
|
||||
|
|
|
@ -78,7 +78,7 @@ function safeJsonParse(jsonString, fallback = null) {
|
|||
}
|
||||
|
||||
try {
|
||||
return extract(jsonString)[0];
|
||||
return extract(jsonString)?.[0] || fallback;
|
||||
} catch {}
|
||||
|
||||
return fallback;
|
||||
|
|
Loading…
Add table
Reference in a new issue