mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-10 17:00:11 +01:00
Init support of i18n and English, Mandarin, Spanish, French (#1317)
* Init support of i18n and English and mandarin * Update common.js (#1320) * add General Appearance and Chat setting zh translate (#1414) * add config zh translate (#1461) * patch some translation pages * Update locality fixes * update: complete login page Mandarin translation. (#1709) update: complete Mandarin translation. * complete translation * update github to run validator * bump to test workflow failure * bump to fix tests * update workflow * refactor lang selector support * add Spanish and French * add dictionaries --------- Co-authored-by: GetOffer.help <13744916+getofferhelp@users.noreply.github.com> Co-authored-by: AIR <129256286+KochabStar@users.noreply.github.com> Co-authored-by: Ezio T <ezio5600@gmail.com>
This commit is contained in:
parent
421c5c6b91
commit
610c87ce19
37
.github/workflows/check-translations.yaml
vendored
Normal file
37
.github/workflows/check-translations.yaml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# This Github action is for validation of all languages which translations are offered for
|
||||
# in the locales folder in `frontend/src`. All languages are compared to the EN translation
|
||||
# schema since that is the fallback language setting. This workflow will run on all PRs that
|
||||
# modify any files in the translation directory
|
||||
name: Verify translations files
|
||||
|
||||
concurrency:
|
||||
group: build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "frontend/src/locales/**.js"
|
||||
|
||||
jobs:
|
||||
run-script:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Run verifyTranslations.mjs script
|
||||
run: |
|
||||
cd frontend/src/locales
|
||||
node verifyTranslations.mjs
|
||||
|
||||
- name: Fail job on error
|
||||
if: failure()
|
||||
run: exit 1
|
@ -19,6 +19,8 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"he": "^1.2.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"i18next": "^23.11.3",
|
||||
"i18next-browser-languagedetector": "^7.2.1",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"markdown-it": "^13.0.1",
|
||||
@ -27,6 +29,7 @@
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-i18next": "^14.1.1",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-speech-recognition": "^3.10.0",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { ContextWrapper } from "@/AuthContext";
|
||||
import PrivateRoute, {
|
||||
AdminRoute,
|
||||
@ -9,6 +10,7 @@ import { ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import Login from "@/pages/Login";
|
||||
import OnboardingFlow from "@/pages/OnboardingFlow";
|
||||
import i18n from "./i18n";
|
||||
|
||||
import { PfpProvider } from "./PfpContext";
|
||||
import { LogoProvider } from "./LogoContext";
|
||||
@ -61,109 +63,113 @@ export default function App() {
|
||||
<ContextWrapper>
|
||||
<LogoProvider>
|
||||
<PfpProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<PrivateRoute Component={Main} />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/workspace/:slug/settings/:tab"
|
||||
element={<ManagerRoute Component={WorkspaceSettings} />}
|
||||
/>
|
||||
<Route
|
||||
path="/workspace/:slug"
|
||||
element={<PrivateRoute Component={WorkspaceChat} />}
|
||||
/>
|
||||
<Route
|
||||
path="/workspace/:slug/t/:threadSlug"
|
||||
element={<PrivateRoute Component={WorkspaceChat} />}
|
||||
/>
|
||||
<Route path="/accept-invite/:code" element={<InvitePage />} />
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Routes>
|
||||
<Route path="/" element={<PrivateRoute Component={Main} />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/workspace/:slug/settings/:tab"
|
||||
element={<ManagerRoute Component={WorkspaceSettings} />}
|
||||
/>
|
||||
<Route
|
||||
path="/workspace/:slug"
|
||||
element={<PrivateRoute Component={WorkspaceChat} />}
|
||||
/>
|
||||
<Route
|
||||
path="/workspace/:slug/t/:threadSlug"
|
||||
element={<PrivateRoute Component={WorkspaceChat} />}
|
||||
/>
|
||||
<Route path="/accept-invite/:code" element={<InvitePage />} />
|
||||
|
||||
{/* Admin */}
|
||||
<Route
|
||||
path="/settings/llm-preference"
|
||||
element={<AdminRoute Component={GeneralLLMPreference} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/transcription-preference"
|
||||
element={
|
||||
<AdminRoute Component={GeneralTranscriptionPreference} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/audio-preference"
|
||||
element={<AdminRoute Component={GeneralAudioPreference} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embedding-preference"
|
||||
element={<AdminRoute Component={GeneralEmbeddingPreference} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/text-splitter-preference"
|
||||
element={
|
||||
<AdminRoute Component={EmbeddingTextSplitterPreference} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/vector-database"
|
||||
element={<AdminRoute Component={GeneralVectorDatabase} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/agents"
|
||||
element={<AdminRoute Component={AdminAgents} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/event-logs"
|
||||
element={<AdminRoute Component={AdminLogs} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embed-config"
|
||||
element={<AdminRoute Component={EmbedConfigSetup} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embed-chats"
|
||||
element={<AdminRoute Component={EmbedChats} />}
|
||||
/>
|
||||
{/* Manager */}
|
||||
<Route
|
||||
path="/settings/security"
|
||||
element={<ManagerRoute Component={GeneralSecurity} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/privacy"
|
||||
element={<AdminRoute Component={PrivacyAndData} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/appearance"
|
||||
element={<ManagerRoute Component={GeneralAppearance} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/api-keys"
|
||||
element={<AdminRoute Component={GeneralApiKeys} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/workspace-chats"
|
||||
element={<ManagerRoute Component={GeneralChats} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/system-preferences"
|
||||
element={<ManagerRoute Component={AdminSystem} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/invites"
|
||||
element={<ManagerRoute Component={AdminInvites} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/users"
|
||||
element={<ManagerRoute Component={AdminUsers} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/workspaces"
|
||||
element={<ManagerRoute Component={AdminWorkspaces} />}
|
||||
/>
|
||||
{/* Onboarding Flow */}
|
||||
<Route path="/onboarding" element={<OnboardingFlow />} />
|
||||
<Route path="/onboarding/:step" element={<OnboardingFlow />} />
|
||||
</Routes>
|
||||
{/* Admin */}
|
||||
<Route
|
||||
path="/settings/llm-preference"
|
||||
element={<AdminRoute Component={GeneralLLMPreference} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/transcription-preference"
|
||||
element={
|
||||
<AdminRoute Component={GeneralTranscriptionPreference} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/audio-preference"
|
||||
element={<AdminRoute Component={GeneralAudioPreference} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embedding-preference"
|
||||
element={
|
||||
<AdminRoute Component={GeneralEmbeddingPreference} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/text-splitter-preference"
|
||||
element={
|
||||
<AdminRoute Component={EmbeddingTextSplitterPreference} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/vector-database"
|
||||
element={<AdminRoute Component={GeneralVectorDatabase} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/agents"
|
||||
element={<AdminRoute Component={AdminAgents} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/event-logs"
|
||||
element={<AdminRoute Component={AdminLogs} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embed-config"
|
||||
element={<AdminRoute Component={EmbedConfigSetup} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/embed-chats"
|
||||
element={<AdminRoute Component={EmbedChats} />}
|
||||
/>
|
||||
{/* Manager */}
|
||||
<Route
|
||||
path="/settings/security"
|
||||
element={<ManagerRoute Component={GeneralSecurity} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/privacy"
|
||||
element={<AdminRoute Component={PrivacyAndData} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/appearance"
|
||||
element={<ManagerRoute Component={GeneralAppearance} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/api-keys"
|
||||
element={<AdminRoute Component={GeneralApiKeys} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/workspace-chats"
|
||||
element={<ManagerRoute Component={GeneralChats} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/system-preferences"
|
||||
element={<ManagerRoute Component={AdminSystem} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/invites"
|
||||
element={<ManagerRoute Component={AdminInvites} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/users"
|
||||
element={<ManagerRoute Component={AdminUsers} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/workspaces"
|
||||
element={<ManagerRoute Component={AdminWorkspaces} />}
|
||||
/>
|
||||
{/* Onboarding Flow */}
|
||||
<Route path="/onboarding" element={<OnboardingFlow />} />
|
||||
<Route path="/onboarding/:step" element={<OnboardingFlow />} />
|
||||
</Routes>
|
||||
</I18nextProvider>
|
||||
<ToastContainer />
|
||||
</PfpProvider>
|
||||
</LogoProvider>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function EditingChatBubble({
|
||||
message,
|
||||
@ -11,11 +12,12 @@ export default function EditingChatBubble({
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [tempMessage, setTempMessage] = useState(message[type]);
|
||||
const isUser = type === "user";
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className={`text-xs text-[#D3D4D4] ${isUser ? "text-right" : ""}`}>
|
||||
{isUser ? "User" : "AnythingLLM Chat Assistant"}
|
||||
{isUser ? t("common.user") : t("appearance.message.assistant")}
|
||||
</p>
|
||||
<div
|
||||
className={`relative flex w-full mt-2 items-start ${
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function NativeEmbeddingOptions() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-full h-10 items-center flex">
|
||||
<p className="text-sm font-base text-white text-opacity-60">
|
||||
There is no set up required when using AnythingLLM's native embedding
|
||||
engine.
|
||||
{t("embedding.provider.description")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import showToast from "@/utils/toast";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import RecoveryCodeModal from "@/components/Modals/DisplayRecoveryCodeModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const RecoveryForm = ({ onSubmit, setShowRecoveryForm }) => {
|
||||
const [username, setUsername] = useState("");
|
||||
@ -160,6 +161,7 @@ const ResetPasswordForm = ({ onSubmit }) => {
|
||||
};
|
||||
|
||||
export default function MultiUserAuth() {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
||||
@ -279,14 +281,15 @@ export default function MultiUserAuth() {
|
||||
<div className="flex items-center flex-col gap-y-4">
|
||||
<div className="flex gap-x-1">
|
||||
<h3 className="text-md md:text-2xl font-bold text-white text-center white-space-nowrap hidden md:block">
|
||||
Welcome to
|
||||
{t("login.multi-user.welcome")}
|
||||
</h3>
|
||||
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
||||
{customAppName || "AnythingLLM"}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm text-white/90 text-center">
|
||||
Sign in to your {customAppName || "AnythingLLM"} account.
|
||||
{t("login.sign-in.start")} {customAppName || "AnythingLLM"}{" "}
|
||||
{t("login.sign-in.end")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -296,7 +299,7 @@ export default function MultiUserAuth() {
|
||||
<input
|
||||
name="username"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
placeholder={t("login.multi-user.placeholder-username")}
|
||||
className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
@ -306,7 +309,7 @@ export default function MultiUserAuth() {
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
placeholder={t("login.multi-user.placeholder-password")}
|
||||
className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
@ -321,14 +324,17 @@ export default function MultiUserAuth() {
|
||||
type="submit"
|
||||
className="md:text-primary-button md:bg-transparent text-dark-text text-sm font-bold focus:ring-4 focus:outline-none rounded-md border-[1.5px] border-[#46C8FF] md:h-[34px] h-[48px] md:hover:text-white md:hover:bg-primary-button bg-primary-button focus:z-10 w-full"
|
||||
>
|
||||
{loading ? "Validating..." : "Login"}
|
||||
{loading
|
||||
? t("login.multi-user.validating")
|
||||
: t("login.multi-user.login")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-white text-sm flex gap-x-1 hover:text-primary-button hover:underline"
|
||||
onClick={handleResetPassword}
|
||||
>
|
||||
Forgot password?<b>Reset</b>
|
||||
{t("login.multi-user.forgot-pass")}?
|
||||
<b>{t("login.multi-user.reset")}</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,8 +5,10 @@ import paths from "../../../utils/paths";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import RecoveryCodeModal from "@/components/Modals/DisplayRecoveryCodeModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SingleUserAuth() {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
||||
@ -73,14 +75,15 @@ export default function SingleUserAuth() {
|
||||
<div className="flex items-center flex-col gap-y-4">
|
||||
<div className="flex gap-x-1">
|
||||
<h3 className="text-md md:text-2xl font-bold text-white text-center white-space-nowrap hidden md:block">
|
||||
Welcome to
|
||||
{t("login.multi-user.welcome")}
|
||||
</h3>
|
||||
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
||||
{customAppName || "AnythingLLM"}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm text-white/90 text-center">
|
||||
Sign in to your {customAppName || "AnythingLLM"} instance.
|
||||
{t("login.sign-in.start")} {customAppName || "AnythingLLM"}{" "}
|
||||
{t("login.sign-in.end")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,8 +29,10 @@ import { USER_BACKGROUND_COLOR } from "@/utils/constants";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import Footer from "../Footer";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SettingsSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const { logo } = useLogo();
|
||||
const { user } = useUser();
|
||||
const sidebarRef = useRef(null);
|
||||
@ -113,7 +115,7 @@ export default function SettingsSidebar() {
|
||||
<div className="h-full flex flex-col w-full justify-between pt-4 overflow-y-scroll no-scroll">
|
||||
<div className="h-auto md:sidebar-items md:dark:sidebar-items">
|
||||
<div className="flex flex-col gap-y-4 pb-[60px] overflow-y-scroll no-scroll">
|
||||
<SidebarOptions user={user} />
|
||||
<SidebarOptions user={user} t={t} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -146,12 +148,12 @@ export default function SettingsSidebar() {
|
||||
>
|
||||
<div className="w-full h-full flex flex-col overflow-x-hidden items-between min-w-[235px]">
|
||||
<div className="text-white text-opacity-60 text-sm font-medium uppercase mt-[4px] mb-0 ml-2">
|
||||
Instance Settings
|
||||
{t("settings.title")}
|
||||
</div>
|
||||
<div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll">
|
||||
<div className="h-auto sidebar-items">
|
||||
<div className="flex flex-col gap-y-2 pb-[60px] overflow-y-scroll no-scroll">
|
||||
<SidebarOptions user={user} />
|
||||
<SidebarOptions user={user} t={t} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -221,39 +223,39 @@ const Option = ({
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarOptions = ({ user = null }) => (
|
||||
const SidebarOptions = ({ user = null, t }) => (
|
||||
<>
|
||||
<Option
|
||||
href={paths.settings.system()}
|
||||
btnText="System Preferences"
|
||||
btnText={t("settings.system")}
|
||||
icon={<SquaresFour className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
allowedRole={["admin", "manager"]}
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.invites()}
|
||||
btnText="Invitation"
|
||||
btnText={t("settings.invites")}
|
||||
icon={<EnvelopeSimple className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
allowedRole={["admin", "manager"]}
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.users()}
|
||||
btnText="Users"
|
||||
btnText={t("settings.users")}
|
||||
icon={<Users className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
allowedRole={["admin", "manager"]}
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.workspaces()}
|
||||
btnText="Workspaces"
|
||||
btnText={t("settings.workspaces")}
|
||||
icon={<BookOpen className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
allowedRole={["admin", "manager"]}
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.chats()}
|
||||
btnText="Workspace Chat"
|
||||
btnText={t("settings.workspace-chats")}
|
||||
icon={<ChatCenteredText className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -270,7 +272,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.appearance()}
|
||||
btnText="Appearance"
|
||||
btnText={t("settings.appearance")}
|
||||
icon={<Eye className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -278,7 +280,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.apiKeys()}
|
||||
btnText="API Keys"
|
||||
btnText={t("settings.api-keys")}
|
||||
icon={<Key className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -286,7 +288,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.llmPreference()}
|
||||
btnText="LLM Preference"
|
||||
btnText={t("settings.llm")}
|
||||
icon={<ChatText className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -302,7 +304,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.transcriptionPreference()}
|
||||
btnText="Transcription Model"
|
||||
btnText={t("settings.transcription")}
|
||||
icon={<ClosedCaptioning className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -311,7 +313,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<Option
|
||||
href={paths.settings.embedder.modelPreference()}
|
||||
childLinks={[paths.settings.embedder.chunkingPreference()]}
|
||||
btnText="Embedder Preferences"
|
||||
btnText={t("settings.embedder")}
|
||||
icon={<FileCode className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -320,7 +322,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<>
|
||||
<Option
|
||||
href={paths.settings.embedder.chunkingPreference()}
|
||||
btnText="Text Splitter & Chunking"
|
||||
btnText={t("settings.text-splitting")}
|
||||
icon={<SplitVertical className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -331,7 +333,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.vectorDatabase()}
|
||||
btnText="Vector Database"
|
||||
btnText={t("settings.vector-database")}
|
||||
icon={<Database className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -340,7 +342,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<Option
|
||||
href={paths.settings.embedSetup()}
|
||||
childLinks={[paths.settings.embedChats()]}
|
||||
btnText="Chat Embed Widgets"
|
||||
btnText={t("settings.embeds")}
|
||||
icon={<CodeBlock className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -349,7 +351,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
<>
|
||||
<Option
|
||||
href={paths.settings.embedChats()}
|
||||
btnText="Chat Embed History"
|
||||
btnText={t("settings.embed-chats")}
|
||||
icon={<Barcode className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -360,7 +362,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.security()}
|
||||
btnText="Security"
|
||||
btnText={t("settings.security")}
|
||||
icon={<Lock className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -369,7 +371,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.logs()}
|
||||
btnText="Event Logs"
|
||||
btnText={t("settings.event-logs")}
|
||||
icon={<Notepad className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
@ -377,7 +379,7 @@ const SidebarOptions = ({ user = null }) => (
|
||||
/>
|
||||
<Option
|
||||
href={paths.settings.privacy()}
|
||||
btnText="Privacy & Data"
|
||||
btnText={t("settings.privacy")}
|
||||
icon={<EyeSlash className="h-5 w-5 flex-shrink-0" />}
|
||||
user={user}
|
||||
flex={true}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Gauge } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Gauge } from "@phosphor-icons/react";
|
||||
|
||||
export default function NativeTranscriptionOptions({ settings }) {
|
||||
const { t } = useTranslation();
|
||||
const [model, setModel] = useState(settings?.WhisperModelPref);
|
||||
|
||||
return (
|
||||
@ -10,7 +12,7 @@ export default function NativeTranscriptionOptions({ settings }) {
|
||||
<div className="w-full flex items-center gap-4">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-4">
|
||||
Model Selection
|
||||
{t("common.selection")}
|
||||
</label>
|
||||
<select
|
||||
name="WhisperModelPref"
|
||||
@ -46,20 +48,19 @@ function LocalWarning({ model }) {
|
||||
}
|
||||
|
||||
function WhisperSmall() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge size={25} />
|
||||
<p className="text-sm">
|
||||
Running the <b>whisper-small</b> model on a machine with limited RAM
|
||||
or CPU can stall AnythingLLM when processing media files.
|
||||
{t("transcription.warn-start")}
|
||||
<br />
|
||||
We recommend at least 2GB of RAM and upload files <10Mb.
|
||||
{t("transcription.warn-recommend")}
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
This model will automatically download on the first use. (250mb)
|
||||
</i>
|
||||
<i>{t("transcription.warn-end")} (250mb)</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,21 +68,19 @@ function WhisperSmall() {
|
||||
}
|
||||
|
||||
function WhisperLarge() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge size={25} />
|
||||
<p className="text-sm">
|
||||
Using the <b>whisper-large</b> model on machines with limited RAM or
|
||||
CPU can stall AnythingLLM when processing media files. This model is
|
||||
substantially larger than the whisper-small.
|
||||
{t("transcription.warn-start")}
|
||||
<br />
|
||||
We recommend at least 8GB of RAM and upload files <10Mb.
|
||||
{t("transcription.warn-recommend")}
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
This model will automatically download on the first use. (1.56GB)
|
||||
</i>
|
||||
<i>{t("transcription.warn-end")} (1.56GB)</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useLanguageOptions } from "@/hooks/useLanguageOptions";
|
||||
import usePfp from "@/hooks/usePfp";
|
||||
import System from "@/models/system";
|
||||
import { AUTH_USER } from "@/utils/constants";
|
||||
@ -147,6 +148,7 @@ export default function AccountModal({ user, hideModal }) {
|
||||
placeholder={`${user.username}'s new password`}
|
||||
/>
|
||||
</div>
|
||||
<LanguagePreference />
|
||||
</div>
|
||||
<div className="flex justify-between items-center border-t border-gray-500/50 pt-4 p-6">
|
||||
<button
|
||||
@ -168,3 +170,37 @@ export default function AccountModal({ user, hideModal }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LanguagePreference() {
|
||||
const {
|
||||
currentLanguage,
|
||||
supportedLanguages,
|
||||
getLanguageName,
|
||||
changeLanguage,
|
||||
} = useLanguageOptions();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor="userLang"
|
||||
className="block mb-2 text-sm font-medium text-white"
|
||||
>
|
||||
Preferred language
|
||||
</label>
|
||||
<select
|
||||
name="userLang"
|
||||
className="bg-zinc-900 w-fit mt-2 px-4 border-gray-500 text-white text-sm rounded-lg block py-2"
|
||||
defaultValue={currentLanguage || "en"}
|
||||
onChange={(e) => changeLanguage(e.target.value)}
|
||||
>
|
||||
{supportedLanguages.map((lang) => {
|
||||
return (
|
||||
<option key={lang} value={lang}>
|
||||
{getLanguageName(lang)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function LanceDBOptions() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-full h-10 items-center flex">
|
||||
<p className="text-sm font-base text-white text-opacity-60">
|
||||
There is no configuration needed for LanceDB.
|
||||
{t("vector.provider.description")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
20
frontend/src/hooks/useLanguageOptions.js
Normal file
20
frontend/src/hooks/useLanguageOptions.js
Normal file
@ -0,0 +1,20 @@
|
||||
import i18n from "@/i18n";
|
||||
import { resources as languages } from "@/locales/resources";
|
||||
|
||||
export function useLanguageOptions() {
|
||||
const supportedLanguages = Object.keys(languages);
|
||||
const languageNames = new Intl.DisplayNames(supportedLanguages, {
|
||||
type: "language",
|
||||
});
|
||||
const changeLanguage = (newLang = "en") => {
|
||||
if (!Object.keys(languages).includes(newLang)) return false;
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
return {
|
||||
currentLanguage: i18n.language || "en",
|
||||
supportedLanguages,
|
||||
getLanguageName: (lang = "en") => languageNames.of(lang),
|
||||
changeLanguage,
|
||||
};
|
||||
}
|
21
frontend/src/i18n.js
Normal file
21
frontend/src/i18n.js
Normal file
@ -0,0 +1,21 @@
|
||||
import i18next from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { defaultNS, resources } from "./locales/resources";
|
||||
|
||||
i18next
|
||||
// https://github.com/i18next/i18next-browser-languageDetector/blob/9efebe6ca0271c3797bc09b84babf1ba2d9b4dbb/src/index.js#L11
|
||||
.use(initReactI18next) // Initialize i18n for React
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
defaultNS,
|
||||
resources,
|
||||
lowerCaseLng: true,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18next;
|
448
frontend/src/locales/en/common.js
Normal file
448
frontend/src/locales/en/common.js
Normal file
@ -0,0 +1,448 @@
|
||||
const TRANSLATIONS = {
|
||||
common: {
|
||||
"workspaces-name": "Workspaces Name",
|
||||
error: "error",
|
||||
success: "success",
|
||||
user: "User",
|
||||
selection: "Model Selection",
|
||||
saving: "Saving...",
|
||||
save: "Save changes",
|
||||
previous: "Previous Page",
|
||||
next: "Next Page",
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Instance Settings",
|
||||
system: "System Preferences",
|
||||
invites: "Invitation",
|
||||
users: "Users",
|
||||
workspaces: "Workspaces",
|
||||
"workspace-chats": "Workspace Chat",
|
||||
appearance: "Appearance",
|
||||
"api-keys": "API Keys",
|
||||
llm: "LLM Preference",
|
||||
transcription: "Transcription Model",
|
||||
embedder: "Embedding Preferences",
|
||||
"text-splitting": "Text Splitter & Chunking",
|
||||
"vector-database": "Vector Database",
|
||||
embeds: "Chat Embed Widgets",
|
||||
"embed-chats": "Chat Embed History",
|
||||
security: "Security",
|
||||
"event-logs": "Event Logs",
|
||||
privacy: "Privacy & Data",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Welcome to",
|
||||
"placeholder-username": "Username",
|
||||
"placeholder-password": "Password",
|
||||
login: "Login",
|
||||
validating: "Validating...",
|
||||
"forgot-pass": "Forgot password",
|
||||
reset: "Reset",
|
||||
},
|
||||
"sign-in": {
|
||||
start: "Sign in to your",
|
||||
end: "account.",
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "General Settings",
|
||||
chat: "Chat Settings",
|
||||
vector: "Vector Database",
|
||||
members: "Members",
|
||||
agent: "Agent Configuration",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Vector Count",
|
||||
description: "Total number of vectors in your vector database.",
|
||||
},
|
||||
names: {
|
||||
description: "This will only change the display name of your workspace.",
|
||||
},
|
||||
message: {
|
||||
title: "Suggested Chat Messages",
|
||||
description:
|
||||
"Customize the messages that will be suggested to your workspace users.",
|
||||
add: "Add new message",
|
||||
save: "Save Messages",
|
||||
heading: "Explain to me",
|
||||
body: "the benefits of AnythingLLM",
|
||||
},
|
||||
pfp: {
|
||||
title: "Assistant Profile Image",
|
||||
description:
|
||||
"Customize the profile image of the assistant for this workspace.",
|
||||
image: "Workspace Image",
|
||||
remove: "Remove Workspace Image",
|
||||
},
|
||||
delete: {
|
||||
delete: "Delete Workspace",
|
||||
deleting: "Deleting Workspace...",
|
||||
"confirm-start": "You are about to delete your entire",
|
||||
"confirm-end":
|
||||
"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",
|
||||
description:
|
||||
"The specific LLM provider & model that will be used for this workspace. By default, it uses the system LLM provider and settings.",
|
||||
search: "Search all LLM providers",
|
||||
},
|
||||
model: {
|
||||
title: "Workspace Chat model",
|
||||
description:
|
||||
"The specific chat model that will be used for this workspace. If empty, will use the system LLM preference.",
|
||||
wait: "-- waiting for models --",
|
||||
},
|
||||
mode: {
|
||||
title: "Chat mode",
|
||||
chat: {
|
||||
title: "Chat",
|
||||
"desc-start": "will provide answers with the LLM's general knowledge",
|
||||
and: "and",
|
||||
"desc-end": "document context that is found.",
|
||||
},
|
||||
query: {
|
||||
title: "Query",
|
||||
"desc-start": "will provide answers",
|
||||
only: "only",
|
||||
"desc-end": "if document context is found.",
|
||||
},
|
||||
},
|
||||
history: {
|
||||
title: "Chat History",
|
||||
"desc-start":
|
||||
"The number of previous chats that will be included in the response's short-term memory.",
|
||||
recommend: "Recommend 20. ",
|
||||
"desc-end":
|
||||
"AAnything more than 45 is likely to lead to continuous chat failures depending on message size.",
|
||||
},
|
||||
prompt: {
|
||||
title: "Prompt",
|
||||
description:
|
||||
"The prompt that will be used on this workspace. Define the context and instructions for the AI to generate a response. You should to provide a carefully crafted prompt so the AI can generate a relevant and accurate response.",
|
||||
},
|
||||
refusal: {
|
||||
title: "Query mode refusal response",
|
||||
"desc-start": "When in",
|
||||
query: "query",
|
||||
"desc-end":
|
||||
"mode, you may want to return a custom refusal response when no context is found.",
|
||||
},
|
||||
temperature: {
|
||||
title: "LLM Temperature",
|
||||
"desc-start":
|
||||
'This setting controls how "creative" your LLM responses will be.',
|
||||
"desc-end":
|
||||
"The higher the number the more creative. For some models this can lead to incoherent responses when set too high.",
|
||||
hint: "Most LLMs have various acceptable ranges of valid values. Consult your LLM provider for that information.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
"vector-workspace": {
|
||||
identifier: "Vector database identifier",
|
||||
snippets: {
|
||||
title: "Max Context Snippets",
|
||||
description:
|
||||
"This setting controls the maximum amount of context snippets the will be sent to the LLM for per chat or query.",
|
||||
recommend: "Recommended: 4",
|
||||
},
|
||||
doc: {
|
||||
title: "Document similarity threshold",
|
||||
description:
|
||||
"The minimum similarity score required for a source to be considered related to the chat. The higher the number, the more similar the source must be to the chat.",
|
||||
zero: "No restriction",
|
||||
low: "Low (similarity score ≥ .25)",
|
||||
medium: "Medium (similarity score ≥ .50)",
|
||||
high: "High (similarity score ≥ .75)",
|
||||
},
|
||||
reset: {
|
||||
reset: "Reset Vector Database",
|
||||
resetting: "Clearing vectors...",
|
||||
confirm:
|
||||
"You are about to reset this workspace's vector database. This will remove all vector embeddings currently embedded.\n\nThe original source files will remain untouched. This action is irreversible.",
|
||||
error: "Workspace vector database could not be reset!",
|
||||
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.",
|
||||
provider: {
|
||||
title: "Workspace Agent LLM Provider",
|
||||
description:
|
||||
"The specific LLM provider & model that will be used for this workspace's @agent agent.",
|
||||
},
|
||||
mode: {
|
||||
chat: {
|
||||
title: "Workspace Agent Chat model",
|
||||
description:
|
||||
"The specific chat model that will be used for this workspace's @agent agent.",
|
||||
},
|
||||
title: "Workspace Agent model",
|
||||
description:
|
||||
"The specific LLM model that will be used for this workspace's @agent agent.",
|
||||
wait: "-- waiting for models --",
|
||||
},
|
||||
|
||||
skill: {
|
||||
title: "Default agent skills",
|
||||
description:
|
||||
"Improve the natural abilities of the default agent with these pre-built skills. This set up applies to all workspaces.",
|
||||
rag: {
|
||||
title: "RAG & long-term memory",
|
||||
description:
|
||||
'Allow the agent to leverage your local documents to answer a query or ask the agent to "remember" pieces of content for long-term memory retrieval.',
|
||||
},
|
||||
view: {
|
||||
title: "View & summarize documents",
|
||||
description:
|
||||
"Allow the agent to list and summarize the content of workspace files currently embedded.",
|
||||
},
|
||||
scrape: {
|
||||
title: "Scrape websites",
|
||||
description:
|
||||
"Allow the agent to visit and scrape the content of websites.",
|
||||
},
|
||||
generate: {
|
||||
title: "Generate charts",
|
||||
description:
|
||||
"Enable the default agent to generate various types of charts from data provided or given in chat.",
|
||||
},
|
||||
save: {
|
||||
title: "Generate & save files to browser",
|
||||
description:
|
||||
"Enable the default agent to generate and write to files that save and can be downloaded in your browser.",
|
||||
},
|
||||
web: {
|
||||
title: "Live web search and browsing",
|
||||
"desc-start":
|
||||
"Enable your agent to search the web to answer your questions by connecting to a web-search (SERP) provider.",
|
||||
"desc-end":
|
||||
"Web search during agent sessions will not work until this is set up.",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Workspace Chats",
|
||||
description:
|
||||
"These are all the recorded chats and messages that have been sent by users ordered by their creation date.",
|
||||
export: "Export",
|
||||
table: {
|
||||
id: "Id",
|
||||
by: "Sent By",
|
||||
workspace: "Workspace",
|
||||
prompt: "Prompt",
|
||||
response: "Response",
|
||||
at: "Sent At",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Appearance",
|
||||
description: "Customize the appearance settings of your platform.",
|
||||
logo: {
|
||||
title: "Customize Logo",
|
||||
description: "Upload your custom logo to make your chatbot yours.",
|
||||
add: "Add a custom logo",
|
||||
recommended: "Recommended size: 800 x 200",
|
||||
remove: "Remove",
|
||||
replace: "Replace",
|
||||
},
|
||||
message: {
|
||||
title: "Customize Messages",
|
||||
description: "Customize the automatic messages displayed to your users.",
|
||||
new: "New",
|
||||
system: "system",
|
||||
user: "user",
|
||||
message: "message",
|
||||
assistant: "AnythingLLM Chat Assistant",
|
||||
"double-click": "Double click to edit...",
|
||||
save: "Save Messages",
|
||||
},
|
||||
icons: {
|
||||
title: "Custom Footer Icons",
|
||||
description:
|
||||
"Customize the footer icons displayed on the bottom of the sidebar.",
|
||||
icon: "Icon",
|
||||
link: "Link",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API Keys",
|
||||
description:
|
||||
"API keys allow the holder to programmatically access and manage this AnythingLLM instance.",
|
||||
link: "Read the API documentation",
|
||||
generate: "Generate New API Key",
|
||||
table: {
|
||||
key: "API Key",
|
||||
by: "Created By",
|
||||
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: "Transcription Model Preference",
|
||||
description:
|
||||
"These are the credentials and settings for your preferred transcription model provider. Its important these keys are current and correct or else media files and audio will not transcribe.",
|
||||
provider: "Transcription Provider",
|
||||
"warn-start":
|
||||
"Using the local whisper model on machines with limited RAM or CPU can stall AnythingLLM when processing media files.",
|
||||
"warn-recommend":
|
||||
"We recommend at least 2GB of RAM and upload files <10Mb.",
|
||||
"warn-end":
|
||||
"The built-in model will automatically download on the first use.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Embedding Preference",
|
||||
"desc-start":
|
||||
"When using an LLM that does not natively support an embedding engine - you may need to additionally specify credentials to for embedding text.",
|
||||
"desc-end":
|
||||
"Embedding is the process of turning text into vectors. These credentials are required to turn your files and prompts into a format which AnythingLLM can use to process.",
|
||||
provider: {
|
||||
title: "Embedding Provider",
|
||||
description:
|
||||
"There is no set up required when using AnythingLLM's native embedding engine.",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "Text splitting & Chunking Preferences",
|
||||
"desc-start":
|
||||
"Sometimes, you may want to change the default way that new documents are split and chunked before being inserted into your vector database.",
|
||||
"desc-end":
|
||||
"You should only modify this setting if you understand how text splitting works and it's side effects.",
|
||||
"warn-start": "Changes here will only apply to",
|
||||
"warn-center": "newly embedded documents",
|
||||
"warn-end": ", not existing documents.",
|
||||
size: {
|
||||
title: "Text Chunk Size",
|
||||
description:
|
||||
"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.",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "Vector Database",
|
||||
description:
|
||||
"These are the credentials and settings for how your AnythingLLM instance will function. It's important these keys are current and correct.",
|
||||
provider: {
|
||||
title: "Vector Database Provider",
|
||||
description: "There is no configuration needed for LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Embeddable Chat Widgets",
|
||||
description:
|
||||
"Embeddable chat widgets are public facing chat interfaces that are tied to a single workspace. These allow you to build workspaces that then you can publish to the world.",
|
||||
create: "Create embed",
|
||||
table: {
|
||||
workspace: "Workspace",
|
||||
chats: "Sent Chats",
|
||||
Active: "Active Domains",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Embed Chats",
|
||||
description:
|
||||
"These are all the recorded chats and messages from any embed that you have published.",
|
||||
table: {
|
||||
embed: "Embed",
|
||||
sender: "Sender",
|
||||
message: "Message",
|
||||
response: "Response",
|
||||
at: "Sent At",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Multi-User Mode",
|
||||
description:
|
||||
"Set up your instance to support your team by activating Multi-User Mode.",
|
||||
enable: {
|
||||
"is-enable": "Multi-User Mode is Enabled",
|
||||
enable: "Enable Multi-User Mode",
|
||||
description:
|
||||
"By default, you will be the only admin. As an admin you will need to create accounts for all new users or admins. Do not lose your password as only an Admin user can reset passwords.",
|
||||
username: "Admin account username",
|
||||
password: "Admin account password",
|
||||
},
|
||||
password: {
|
||||
title: "Password Protection",
|
||||
description:
|
||||
"Protect your AnythingLLM instance with a password. If you forget this there is no recovery method so ensure you save this password.",
|
||||
},
|
||||
instance: {
|
||||
title: "Password Protect Instance",
|
||||
description:
|
||||
"By default, you will be the only admin. As an admin you will need to create accounts for all new users or admins. Do not lose your password as only an Admin user can reset passwords.",
|
||||
password: "Instance password",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Event Logs",
|
||||
description:
|
||||
"View all actions and events happening on this instance for monitoring.",
|
||||
clear: "Clear Event Logs",
|
||||
table: {
|
||||
type: "Event Type",
|
||||
user: "User",
|
||||
occurred: "Occurred At",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Privacy & Data-Handling",
|
||||
description:
|
||||
"This is your configuration for how connected third party providers and AnythingLLM handle your data.",
|
||||
llm: "LLM Selection",
|
||||
embedding: "Embedding Preference",
|
||||
vector: "Vector Database",
|
||||
anonymous: "Anonymous Telemetry Enabled",
|
||||
},
|
||||
};
|
||||
|
||||
export default TRANSLATIONS;
|
440
frontend/src/locales/es/common.js
Normal file
440
frontend/src/locales/es/common.js
Normal file
@ -0,0 +1,440 @@
|
||||
const TRANSLATIONS = {
|
||||
common: {
|
||||
"workspaces-name": "Nombre de espacios de trabajo",
|
||||
error: "error",
|
||||
success: "éxito",
|
||||
user: "Usuario",
|
||||
selection: "Selección de modelo",
|
||||
saving: "Guardando...",
|
||||
save: "Guardar cambios",
|
||||
previous: "Página anterior",
|
||||
next: "Página siguiente",
|
||||
},
|
||||
|
||||
settings: {
|
||||
title: "Configuración de instancia",
|
||||
system: "Preferencias del sistema",
|
||||
invites: "Invitación",
|
||||
users: "Usuarios",
|
||||
workspaces: "Espacios de trabajo",
|
||||
"workspace-chats": "Chat del espacio de trabajo",
|
||||
appearance: "Apariencia",
|
||||
"api-keys": "Claves API",
|
||||
llm: "Preferencia de LLM",
|
||||
transcription: "Modelo de transcripción",
|
||||
embedder: "Preferencias de incrustación",
|
||||
"text-splitting": "Divisor y fragmentación de texto",
|
||||
"vector-database": "Base de datos de vectores",
|
||||
embeds: "Widgets de chat incrustados",
|
||||
"embed-chats": "Historial de chats incrustados",
|
||||
security: "Seguridad",
|
||||
"event-logs": "Registros de eventos",
|
||||
privacy: "Privacidad y datos",
|
||||
},
|
||||
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Bienvenido a",
|
||||
"placeholder-username": "Nombre de usuario",
|
||||
"placeholder-password": "Contraseña",
|
||||
login: "Iniciar sesión",
|
||||
validating: "Validando...",
|
||||
"forgot-pass": "Olvidé mi contraseña",
|
||||
reset: "Restablecer",
|
||||
},
|
||||
"sign-in": {
|
||||
start: "Iniciar sesión en tu",
|
||||
end: "cuenta.",
|
||||
},
|
||||
},
|
||||
|
||||
"workspaces—settings": {
|
||||
general: "Configuración general",
|
||||
chat: "Configuración de chat",
|
||||
vector: "Base de datos de vectores",
|
||||
members: "Miembros",
|
||||
agent: "Configuración del agente",
|
||||
},
|
||||
|
||||
general: {
|
||||
vector: {
|
||||
title: "Conteo de vectores",
|
||||
description: "Número total de vectores en tu base de datos de vectores.",
|
||||
},
|
||||
names: {
|
||||
description:
|
||||
"Esto solo cambiará el nombre de visualización de tu espacio de trabajo.",
|
||||
},
|
||||
message: {
|
||||
title: "Mensajes de chat sugeridos",
|
||||
description:
|
||||
"Personaliza los mensajes que se sugerirán a los usuarios de tu espacio de trabajo.",
|
||||
add: "Agregar nuevo mensaje",
|
||||
save: "Guardar mensajes",
|
||||
heading: "Explícame",
|
||||
body: "los beneficios de AnythingLLM",
|
||||
},
|
||||
pfp: {
|
||||
title: "Imagen de perfil del asistente",
|
||||
description:
|
||||
"Personaliza la imagen de perfil del asistente para este espacio de trabajo.",
|
||||
image: "Imagen del espacio de trabajo",
|
||||
remove: "Eliminar imagen del espacio de trabajo",
|
||||
},
|
||||
delete: {
|
||||
delete: "Eliminar espacio de trabajo",
|
||||
deleting: "Eliminando espacio de trabajo...",
|
||||
"confirm-start": "Estás a punto de eliminar tu",
|
||||
"confirm-end":
|
||||
"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",
|
||||
description:
|
||||
"El proveedor y modelo LLM específico que se utilizará para este espacio de trabajo. Por defecto, utiliza el proveedor y configuración del sistema LLM.",
|
||||
search: "Buscar todos los proveedores LLM",
|
||||
},
|
||||
model: {
|
||||
title: "Modelo de chat del espacio de trabajo",
|
||||
description:
|
||||
"El modelo de chat específico que se utilizará para este espacio de trabajo. Si está vacío, se utilizará la preferencia LLM del sistema.",
|
||||
wait: "-- esperando modelos --",
|
||||
},
|
||||
mode: {
|
||||
title: "Modo de chat",
|
||||
chat: {
|
||||
title: "Chat",
|
||||
"desc-start":
|
||||
"proporcionará respuestas con el conocimiento general del LLM",
|
||||
and: "y",
|
||||
"desc-end": "el contexto del documento que se encuentre.",
|
||||
},
|
||||
query: {
|
||||
title: "Consulta",
|
||||
"desc-start": "proporcionará respuestas",
|
||||
only: "solo",
|
||||
"desc-end": "si se encuentra el contexto del documento.",
|
||||
},
|
||||
},
|
||||
history: {
|
||||
title: "Historial de chat",
|
||||
"desc-start":
|
||||
"El número de chats anteriores que se incluirán en la memoria a corto plazo de la respuesta.",
|
||||
recommend: "Recomendar 20. ",
|
||||
"desc-end":
|
||||
"Cualquier cosa más de 45 probablemente conducirá a fallos continuos en el chat dependiendo del tamaño del mensaje.",
|
||||
},
|
||||
prompt: {
|
||||
title: "Prompt",
|
||||
description:
|
||||
"El prompt que se utilizará en este espacio de trabajo. Define el contexto y las instrucciones para que la IA genere una respuesta. Debes proporcionar un prompt cuidadosamente elaborado para que la IA pueda generar una respuesta relevante y precisa.",
|
||||
},
|
||||
refusal: {
|
||||
title: "Respuesta de rechazo en modo consulta",
|
||||
"desc-start": "Cuando esté en",
|
||||
query: "consulta",
|
||||
"desc-end":
|
||||
"modo, es posible que desees devolver una respuesta de rechazo personalizada cuando no se encuentre contexto.",
|
||||
},
|
||||
temperature: {
|
||||
title: "Temperatura de LLM",
|
||||
"desc-start":
|
||||
'Esta configuración controla cuán "creativas" serán las respuestas de tu LLM.',
|
||||
"desc-end":
|
||||
"Cuanto mayor sea el número, más creativas serán las respuestas. Para algunos modelos, esto puede llevar a respuestas incoherentes cuando se establece demasiado alto.",
|
||||
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: {
|
||||
title: "Máximo de fragmentos de contexto",
|
||||
description:
|
||||
"Esta configuración controla la cantidad máxima de fragmentos de contexto que se enviarán al LLM por chat o consulta.",
|
||||
recommend: "Recomendado: 4",
|
||||
},
|
||||
doc: {
|
||||
title: "Umbral de similitud de documentos",
|
||||
description:
|
||||
"La puntuación mínima de similitud requerida para que una fuente se considere relacionada con el chat. Cuanto mayor sea el número, más similar debe ser la fuente al chat.",
|
||||
zero: "Sin restricción",
|
||||
low: "Bajo (puntuación de similitud ≥ .25)",
|
||||
medium: "Medio (puntuación de similitud ≥ .50)",
|
||||
high: "Alto (puntuación de similitud ≥ .75)",
|
||||
},
|
||||
reset: {
|
||||
reset: "Restablecer la base de datos de vectores",
|
||||
resetting: "Borrando vectores...",
|
||||
confirm:
|
||||
"Estás a punto de restablecer la base de datos de vectores de este espacio de trabajo. Esto eliminará todas las incrustaciones de vectores actualmente incrustadas.\n\nLos archivos de origen originales permanecerán intactos. Esta acción es irreversible.",
|
||||
error:
|
||||
"¡No se pudo restablecer la base de datos de vectores del espacio de trabajo!",
|
||||
success:
|
||||
"¡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.",
|
||||
provider: {
|
||||
title: "Proveedor de LLM del agente del espacio de trabajo",
|
||||
description:
|
||||
"El proveedor y modelo LLM específico que se utilizará para el agente @agent de este espacio de trabajo.",
|
||||
},
|
||||
mode: {
|
||||
chat: {
|
||||
title: "Modelo de chat del agente del espacio de trabajo",
|
||||
description:
|
||||
"El modelo de chat específico que se utilizará para el agente @agent de este espacio de trabajo.",
|
||||
},
|
||||
title: "Modelo del agente del espacio de trabajo",
|
||||
description:
|
||||
"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:
|
||||
"Mejora las habilidades naturales del agente predeterminado con estas habilidades preconstruidas. Esta configuración se aplica a todos los espacios de trabajo.",
|
||||
rag: {
|
||||
title: "RAG y memoria a largo plazo",
|
||||
description:
|
||||
'Permitir que el agente aproveche tus documentos locales para responder a una consulta o pedirle al agente que "recuerde" piezas de contenido para la recuperación de memoria a largo plazo.',
|
||||
},
|
||||
view: {
|
||||
title: "Ver y resumir documentos",
|
||||
description:
|
||||
"Permitir que el agente enumere y resuma el contenido de los archivos del espacio de trabajo actualmente incrustados.",
|
||||
},
|
||||
scrape: {
|
||||
title: "Rastrear sitios web",
|
||||
description:
|
||||
"Permitir que el agente visite y rastree el contenido de sitios web.",
|
||||
},
|
||||
generate: {
|
||||
title: "Generar gráficos",
|
||||
description:
|
||||
"Habilitar al agente predeterminado para generar varios tipos de gráficos a partir de datos proporcionados o dados en el chat.",
|
||||
},
|
||||
save: {
|
||||
title: "Generar y guardar archivos en el navegador",
|
||||
description:
|
||||
"Habilitar al agente predeterminado para generar y escribir archivos que se guarden y puedan descargarse en tu navegador.",
|
||||
},
|
||||
web: {
|
||||
title: "Búsqueda en vivo en la web y navegación",
|
||||
"desc-start":
|
||||
"Permitir que tu agente busque en la web para responder tus preguntas conectándose a un proveedor de búsqueda en la web (SERP).",
|
||||
"desc-end":
|
||||
"La búsqueda en la web durante las sesiones del agente no funcionará hasta que esto esté configurado.",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
recorded: {
|
||||
title: "Chats del espacio de trabajo",
|
||||
description:
|
||||
"Estos son todos los chats y mensajes grabados que han sido enviados por los usuarios ordenados por su fecha de creación.",
|
||||
export: "Exportar",
|
||||
table: {
|
||||
id: "Id",
|
||||
by: "Enviado por",
|
||||
workspace: "Espacio de trabajo",
|
||||
prompt: "Prompt",
|
||||
response: "Respuesta",
|
||||
at: "Enviado a",
|
||||
},
|
||||
},
|
||||
|
||||
appearance: {
|
||||
title: "Apariencia",
|
||||
description: "Personaliza la configuración de apariencia de tu plataforma.",
|
||||
logo: {
|
||||
title: "Personalizar logotipo",
|
||||
description:
|
||||
"Sube tu logotipo personalizado para hacer que tu chatbot sea tuyo.",
|
||||
add: "Agregar un logotipo personalizado",
|
||||
recommended: "Tamaño recomendado: 800 x 200",
|
||||
remove: "Eliminar",
|
||||
replace: "Reemplazar",
|
||||
},
|
||||
message: {
|
||||
title: "Personalizar mensajes",
|
||||
description:
|
||||
"Personaliza los mensajes automáticos que se muestran a tus usuarios.",
|
||||
new: "Nuevo",
|
||||
system: "sistema",
|
||||
user: "usuario",
|
||||
message: "mensaje",
|
||||
assistant: "Asistente de chat AnythingLLM",
|
||||
"double-click": "Haz doble clic para editar...",
|
||||
save: "Guardar mensajes",
|
||||
},
|
||||
icons: {
|
||||
title: "Iconos de pie de página personalizados",
|
||||
description:
|
||||
"Personaliza los iconos de pie de página que se muestran en la parte inferior de la barra lateral.",
|
||||
icon: "Icono",
|
||||
link: "Enlace",
|
||||
},
|
||||
},
|
||||
|
||||
api: {
|
||||
title: "Claves API",
|
||||
description:
|
||||
"Las claves API permiten al titular acceder y gestionar programáticamente esta instancia de AnythingLLM.",
|
||||
link: "Leer la documentación de la API",
|
||||
generate: "Generar nueva clave API",
|
||||
table: {
|
||||
key: "Clave API",
|
||||
by: "Creado por",
|
||||
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:
|
||||
"Estas son las credenciales y configuraciones para tu proveedor preferido de modelo de transcripción. Es importante que estas claves estén actualizadas y correctas, de lo contrario los archivos multimedia y de audio no se transcribirán.",
|
||||
provider: "Proveedor de transcripción",
|
||||
"warn-start":
|
||||
"El uso del modelo local Whisper en máquinas con RAM o CPU limitadas puede bloquear AnythingLLM al procesar archivos multimedia.",
|
||||
"warn-recommend":
|
||||
"Recomendamos al menos 2GB de RAM y subir archivos <10Mb.",
|
||||
"warn-end":
|
||||
"El modelo incorporado se descargará automáticamente en el primer uso.",
|
||||
},
|
||||
|
||||
embedding: {
|
||||
title: "Preferencia de incrustación",
|
||||
"desc-start":
|
||||
"Cuando uses un LLM que no admita de forma nativa un motor de incrustación, es posible que necesites especificar credenciales adicionales para incrustar texto.",
|
||||
"desc-end":
|
||||
"La incrustación es el proceso de convertir texto en vectores. Estas credenciales son necesarias para convertir tus archivos y prompts en un formato que AnythingLLM pueda usar para procesar.",
|
||||
provider: {
|
||||
title: "Proveedor de incrustación",
|
||||
description:
|
||||
"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":
|
||||
"A veces, es posible que desees cambiar la forma predeterminada en que los nuevos documentos se dividen y fragmentan antes de ser insertados en tu base de datos de vectores.",
|
||||
"desc-end":
|
||||
"Solo debes modificar esta configuración si entiendes cómo funciona la división de texto y sus efectos secundarios.",
|
||||
"warn-start": "Los cambios aquí solo se aplicarán a",
|
||||
"warn-center": "documentos recién incrustados",
|
||||
"warn-end": ", no a los documentos existentes.",
|
||||
size: {
|
||||
title: "Tamaño del fragmento de texto",
|
||||
description:
|
||||
"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:
|
||||
"Estas son las credenciales y configuraciones para cómo funcionará tu instancia de AnythingLLM. Es importante que estas claves estén actualizadas y correctas.",
|
||||
provider: {
|
||||
title: "Proveedor de base de datos de vectores",
|
||||
description: "No se necesita configuración para LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
embeddable: {
|
||||
title: "Widgets de chat incrustables",
|
||||
description:
|
||||
"Los widgets de chat incrustables son interfaces de chat de cara al público que están vinculadas a un solo espacio de trabajo. Esto te permite crear espacios de trabajo que luego puedes publicar al mundo.",
|
||||
create: "Crear incrustación",
|
||||
table: {
|
||||
workspace: "Espacio de trabajo",
|
||||
chats: "Chats enviados",
|
||||
Active: "Dominios activos",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Incrustar chats",
|
||||
description:
|
||||
"Estos son todos los chats y mensajes grabados de cualquier incrustación que hayas publicado.",
|
||||
table: {
|
||||
embed: "Incrustar",
|
||||
sender: "Remitente",
|
||||
message: "Mensaje",
|
||||
response: "Respuesta",
|
||||
at: "Enviado a",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Modo multiusuario",
|
||||
description:
|
||||
"Configura tu instancia para admitir a tu equipo activando el modo multiusuario.",
|
||||
enable: {
|
||||
"is-enable": "El modo multiusuario está habilitado",
|
||||
enable: "Habilitar modo multiusuario",
|
||||
description:
|
||||
"Por defecto, serás el único administrador. Como administrador, necesitarás crear cuentas para todos los nuevos usuarios o administradores. No pierdas tu contraseña ya que solo un usuario administrador puede restablecer las contraseñas.",
|
||||
username: "Nombre de usuario de la cuenta de administrador",
|
||||
password: "Contraseña de la cuenta de administrador",
|
||||
},
|
||||
password: {
|
||||
title: "Protección con contraseña",
|
||||
description:
|
||||
"Protege tu instancia de AnythingLLM con una contraseña. Si olvidas esta contraseña, no hay método de recuperación, así que asegúrate de guardar esta contraseña.",
|
||||
},
|
||||
instance: {
|
||||
title: "Proteger instancia con contraseña",
|
||||
description:
|
||||
"Por defecto, serás el único administrador. Como administrador, necesitarás crear cuentas para todos los nuevos usuarios o administradores. No pierdas tu contraseña ya que solo un usuario administrador puede restablecer las contraseñas.",
|
||||
password: "Contraseña de la instancia",
|
||||
},
|
||||
},
|
||||
|
||||
event: {
|
||||
title: "Registros de eventos",
|
||||
description:
|
||||
"Ver todas las acciones y eventos que ocurren en esta instancia para monitoreo.",
|
||||
clear: "Borrar registros de eventos",
|
||||
table: {
|
||||
type: "Tipo de evento",
|
||||
user: "Usuario",
|
||||
occurred: "Ocurrido a",
|
||||
},
|
||||
},
|
||||
|
||||
privacy: {
|
||||
title: "Privacidad y manejo de datos",
|
||||
description:
|
||||
"Esta es tu configuración para cómo los proveedores de terceros conectados y AnythingLLM manejan tus datos.",
|
||||
llm: "Selección de LLM",
|
||||
embedding: "Preferencia de incrustación",
|
||||
vector: "Base de datos de vectores",
|
||||
anonymous: "Telemetría anónima habilitada",
|
||||
},
|
||||
};
|
||||
|
||||
export default TRANSLATIONS;
|
456
frontend/src/locales/fr/common.js
Normal file
456
frontend/src/locales/fr/common.js
Normal file
@ -0,0 +1,456 @@
|
||||
const TRANSLATIONS = {
|
||||
common: {
|
||||
"workspaces-name": "Nom des espaces de travail",
|
||||
error: "erreur",
|
||||
success: "succès",
|
||||
user: "Utilisateur",
|
||||
selection: "Sélection du modèle",
|
||||
saving: "Enregistrement...",
|
||||
save: "Enregistrer les modifications",
|
||||
previous: "Page précédente",
|
||||
next: "Page suivante",
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "Paramètres de l'instance",
|
||||
system: "Préférences système",
|
||||
invites: "Invitation",
|
||||
users: "Utilisateurs",
|
||||
workspaces: "Espaces de travail",
|
||||
"workspace-chats": "Chat de l'espace de travail",
|
||||
appearance: "Apparence",
|
||||
"api-keys": "Clés API",
|
||||
llm: "Préférence LLM",
|
||||
transcription: "Modèle de transcription",
|
||||
embedder: "Préférences d'intégration",
|
||||
"text-splitting": "Diviseur de texte et découpage",
|
||||
"vector-database": "Base de données vectorielle",
|
||||
embeds: "Widgets de chat intégrés",
|
||||
"embed-chats": "Historique des chats intégrés",
|
||||
security: "Sécurité",
|
||||
"event-logs": "Journaux d'événements",
|
||||
privacy: "Confidentialité et données",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "Bienvenue à",
|
||||
"placeholder-username": "Nom d'utilisateur",
|
||||
"placeholder-password": "Mot de passe",
|
||||
login: "Connexion",
|
||||
validating: "Validation...",
|
||||
"forgot-pass": "Mot de passe oublié",
|
||||
reset: "Réinitialiser",
|
||||
},
|
||||
"sign-in": {
|
||||
start: "Connectez-vous à votre",
|
||||
end: "compte.",
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "Paramètres généraux",
|
||||
chat: "Paramètres de chat",
|
||||
vector: "Base de données vectorielle",
|
||||
members: "Membres",
|
||||
agent: "Configuration de l'agent",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "Nombre de vecteurs",
|
||||
description:
|
||||
"Nombre total de vecteurs dans votre base de données vectorielle.",
|
||||
},
|
||||
names: {
|
||||
description:
|
||||
"Cela ne changera que le nom d'affichage de votre espace de travail.",
|
||||
},
|
||||
message: {
|
||||
title: "Messages de chat suggérés",
|
||||
description:
|
||||
"Personnalisez les messages qui seront suggérés aux utilisateurs de votre espace de travail.",
|
||||
add: "Ajouter un nouveau message",
|
||||
save: "Enregistrer les messages",
|
||||
heading: "Expliquez-moi",
|
||||
body: "les avantages de AnythingLLM",
|
||||
},
|
||||
pfp: {
|
||||
title: "Image de profil de l'assistant",
|
||||
description:
|
||||
"Personnalisez l'image de profil de l'assistant pour cet espace de travail.",
|
||||
image: "Image de l'espace de travail",
|
||||
remove: "Supprimer l'image de l'espace de travail",
|
||||
},
|
||||
delete: {
|
||||
delete: "Supprimer l'espace de travail",
|
||||
deleting: "Suppression de l'espace de travail...",
|
||||
"confirm-start": "Vous êtes sur le point de supprimer votre",
|
||||
"confirm-end":
|
||||
"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",
|
||||
description:
|
||||
"Le fournisseur et le modèle LLM spécifiques qui seront utilisés pour cet espace de travail. Par défaut, il utilise le fournisseur et les paramètres LLM du système.",
|
||||
search: "Rechercher tous les fournisseurs LLM",
|
||||
},
|
||||
model: {
|
||||
title: "Modèle de chat de l'espace de travail",
|
||||
description:
|
||||
"Le modèle de chat spécifique qui sera utilisé pour cet espace de travail. Si vide, utilisera la préférence LLM du système.",
|
||||
wait: "-- en attente des modèles --",
|
||||
},
|
||||
mode: {
|
||||
title: "Mode de chat",
|
||||
chat: {
|
||||
title: "Chat",
|
||||
"desc-start":
|
||||
"fournira des réponses avec les connaissances générales du LLM",
|
||||
and: "et",
|
||||
"desc-end": "le contexte du document trouvé.",
|
||||
},
|
||||
query: {
|
||||
title: "Requête",
|
||||
"desc-start": "fournira des réponses",
|
||||
only: "uniquement",
|
||||
"desc-end": "si un contexte de document est trouvé.",
|
||||
},
|
||||
},
|
||||
history: {
|
||||
title: "Historique des chats",
|
||||
"desc-start":
|
||||
"Le nombre de chats précédents qui seront inclus dans la mémoire à court terme de la réponse.",
|
||||
recommend: "Recommandé: 20.",
|
||||
"desc-end":
|
||||
"Tout nombre supérieur à 45 risque de provoquer des échecs de chat continus en fonction de la taille du message.",
|
||||
},
|
||||
prompt: {
|
||||
title: "Invite",
|
||||
description:
|
||||
"L'invite qui sera utilisée sur cet espace de travail. Définissez le contexte et les instructions pour que l'IA génère une réponse. Vous devez fournir une invite soigneusement conçue pour que l'IA puisse générer une réponse pertinente et précise.",
|
||||
},
|
||||
refusal: {
|
||||
title: "Réponse de refus en mode requête",
|
||||
"desc-start": "En mode",
|
||||
query: "requête",
|
||||
"desc-end":
|
||||
", vous pouvez souhaiter retourner une réponse de refus personnalisée lorsque aucun contexte n'est trouvé.",
|
||||
},
|
||||
temperature: {
|
||||
title: "Température LLM",
|
||||
"desc-start":
|
||||
"Ce paramètre contrôle le niveau de créativité des réponses de votre LLM.",
|
||||
"desc-end":
|
||||
"Plus le nombre est élevé, plus la réponse sera créative. Pour certains modèles, cela peut entraîner des réponses incohérentes si la valeur est trop élevée.",
|
||||
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: {
|
||||
title: "Nombre maximum de contextes",
|
||||
description:
|
||||
"Ce paramètre contrôle le nombre maximum de contextes qui seront envoyés au LLM par chat ou requête.",
|
||||
recommend: "Recommandé: 4",
|
||||
},
|
||||
doc: {
|
||||
title: "Seuil de similarité des documents",
|
||||
description:
|
||||
"Le score de similarité minimum requis pour qu'une source soit considérée comme liée au chat. Plus le nombre est élevé, plus la source doit être similaire au chat.",
|
||||
zero: "Aucune restriction",
|
||||
low: "Bas (score de similarité ≥ .25)",
|
||||
medium: "Moyen (score de similarité ≥ .50)",
|
||||
high: "Élevé (score de similarité ≥ .75)",
|
||||
},
|
||||
reset: {
|
||||
reset: "Réinitialiser la base de données vectorielle",
|
||||
resetting: "Effacement des vecteurs...",
|
||||
confirm:
|
||||
"Vous êtes sur le point de réinitialiser la base de données vectorielle de cet espace de travail. Cela supprimera toutes les intégrations vectorielles actuellement intégrées.\n\nLes fichiers source originaux resteront intacts. Cette action est irréversible.",
|
||||
error:
|
||||
"La base de données vectorielle de l'espace de travail n'a pas pu être réinitialisée !",
|
||||
success:
|
||||
"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.",
|
||||
provider: {
|
||||
title: "Fournisseur LLM de l'agent de l'espace de travail",
|
||||
description:
|
||||
"Le fournisseur et le modèle LLM spécifiques qui seront utilisés pour l'agent @agent de cet espace de travail.",
|
||||
},
|
||||
mode: {
|
||||
chat: {
|
||||
title: "Modèle de chat de l'agent de l'espace de travail",
|
||||
description:
|
||||
"Le modèle de chat spécifique qui sera utilisé pour l'agent @agent de cet espace de travail.",
|
||||
},
|
||||
title: "Modèle de l'agent de l'espace de travail",
|
||||
description:
|
||||
"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:
|
||||
"Améliorez les capacités naturelles de l'agent par défaut avec ces compétences préconstruites. Cette configuration s'applique à tous les espaces de travail.",
|
||||
rag: {
|
||||
title: "RAG et mémoire à long terme",
|
||||
description:
|
||||
"Permettez à l'agent de s'appuyer sur vos documents locaux pour répondre à une requête ou demandez à l'agent de se souvenir de morceaux de contenu pour la récupération de mémoire à long terme.",
|
||||
},
|
||||
view: {
|
||||
title: "Voir et résumer des documents",
|
||||
description:
|
||||
"Permettez à l'agent de lister et de résumer le contenu des fichiers de l'espace de travail actuellement intégrés.",
|
||||
},
|
||||
scrape: {
|
||||
title: "Récupérer des sites web",
|
||||
description:
|
||||
"Permettez à l'agent de visiter et de récupérer le contenu des sites web.",
|
||||
},
|
||||
generate: {
|
||||
title: "Générer des graphiques",
|
||||
description:
|
||||
"Activez l'agent par défaut pour générer différents types de graphiques à partir des données fournies ou données dans le chat.",
|
||||
},
|
||||
save: {
|
||||
title: "Générer et sauvegarder des fichiers dans le navigateur",
|
||||
description:
|
||||
"Activez l'agent par défaut pour générer et écrire des fichiers qui peuvent être sauvegardés et téléchargés dans votre navigateur.",
|
||||
},
|
||||
web: {
|
||||
title: "Recherche web en direct et navigation",
|
||||
"desc-start":
|
||||
"Permettez à votre agent de rechercher sur le web pour répondre à vos questions en se connectant à un fournisseur de recherche web (SERP).",
|
||||
"desc-end":
|
||||
"La recherche web pendant les sessions d'agent ne fonctionnera pas tant que cela ne sera pas configuré.",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chats
|
||||
recorded: {
|
||||
title: "Chats de l'espace de travail",
|
||||
description:
|
||||
"Voici tous les chats et messages enregistrés qui ont été envoyés par les utilisateurs, classés par date de création.",
|
||||
export: "Exporter",
|
||||
table: {
|
||||
id: "Id",
|
||||
by: "Envoyé par",
|
||||
workspace: "Espace de travail",
|
||||
prompt: "Invite",
|
||||
response: "Réponse",
|
||||
at: "Envoyé à",
|
||||
},
|
||||
},
|
||||
|
||||
// Appearance
|
||||
appearance: {
|
||||
title: "Apparence",
|
||||
description:
|
||||
"Personnalisez les paramètres d'apparence de votre plateforme.",
|
||||
logo: {
|
||||
title: "Personnaliser le logo",
|
||||
description:
|
||||
"Téléchargez votre logo personnalisé pour rendre votre chatbot unique.",
|
||||
add: "Ajouter un logo personnalisé",
|
||||
recommended: "Taille recommandée: 800 x 200",
|
||||
remove: "Supprimer",
|
||||
replace: "Remplacer",
|
||||
},
|
||||
message: {
|
||||
title: "Personnaliser les messages",
|
||||
description:
|
||||
"Personnalisez les messages automatiques affichés à vos utilisateurs.",
|
||||
new: "Nouveau",
|
||||
system: "système",
|
||||
user: "utilisateur",
|
||||
message: "message",
|
||||
assistant: "Assistant de chat AnythingLLM",
|
||||
"double-click": "Double-cliquez pour modifier...",
|
||||
save: "Enregistrer les messages",
|
||||
},
|
||||
icons: {
|
||||
title: "Icônes de pied de page personnalisées",
|
||||
description:
|
||||
"Personnalisez les icônes de pied de page affichées en bas de la barre latérale.",
|
||||
icon: "Icône",
|
||||
link: "Lien",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "Clés API",
|
||||
description:
|
||||
"Les clés API permettent au titulaire d'accéder et de gérer de manière programmatique cette instance AnythingLLM.",
|
||||
link: "Lisez la documentation de l'API",
|
||||
generate: "Générer une nouvelle clé API",
|
||||
table: {
|
||||
key: "Clé API",
|
||||
by: "Créé par",
|
||||
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:
|
||||
"Voici les identifiants et les paramètres de votre fournisseur de modèle de transcription préféré. Il est important que ces clés soient actuelles et correctes, sinon les fichiers multimédias et audio ne seront pas transcrits.",
|
||||
provider: "Fournisseur de transcription",
|
||||
"warn-start":
|
||||
"L'utilisation du modèle local whisper sur des machines avec une RAM ou un CPU limités peut bloquer AnythingLLM lors du traitement des fichiers multimédias.",
|
||||
"warn-recommend":
|
||||
"Nous recommandons au moins 2 Go de RAM et des fichiers téléchargés <10 Mo.",
|
||||
"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":
|
||||
"Lorsque vous utilisez un LLM qui ne supporte pas nativement un moteur d'intégration - vous devrez peut-être spécifier en plus des identifiants pour intégrer le texte.",
|
||||
"desc-end":
|
||||
"L'intégration est le processus de transformation du texte en vecteurs. Ces identifiants sont nécessaires pour transformer vos fichiers et invites en un format que AnythingLLM peut utiliser pour traiter.",
|
||||
provider: {
|
||||
title: "Fournisseur d'intégration",
|
||||
description:
|
||||
"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":
|
||||
"Parfois, vous voudrez peut-être changer la façon dont les nouveaux documents sont divisés et découpés avant d'être insérés dans votre base de données vectorielle.",
|
||||
"desc-end":
|
||||
"Vous ne devez modifier ce paramètre que si vous comprenez comment fonctionne la division du texte et ses effets secondaires.",
|
||||
"warn-start": "Les changements ici s'appliqueront uniquement aux",
|
||||
"warn-center": "nouveaux documents intégrés",
|
||||
"warn-end": ", pas aux documents existants.",
|
||||
size: {
|
||||
title: "Taille des segments de texte",
|
||||
description:
|
||||
"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:
|
||||
"Voici les identifiants et les paramètres de fonctionnement de votre instance AnythingLLM. Il est important que ces clés soient actuelles et correctes.",
|
||||
provider: {
|
||||
title: "Fournisseur de base de données vectorielle",
|
||||
description: "Aucune configuration n'est nécessaire pour LanceDB.",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat Widgets
|
||||
embeddable: {
|
||||
title: "Widgets de chat intégrables",
|
||||
description:
|
||||
"Les widgets de chat intégrables sont des interfaces de chat publiques associées à un espace de travail unique. Ils vous permettent de créer des espaces de travail que vous pouvez ensuite publier dans le monde entier.",
|
||||
create: "Créer un widget intégré",
|
||||
table: {
|
||||
workspace: "Espace de travail",
|
||||
chats: "Chats envoyés",
|
||||
Active: "Domaines actifs",
|
||||
},
|
||||
},
|
||||
|
||||
"embed-chats": {
|
||||
title: "Chats intégrés",
|
||||
description:
|
||||
"Voici tous les chats et messages enregistrés de tout widget intégré que vous avez publié.",
|
||||
table: {
|
||||
embed: "Intégration",
|
||||
sender: "Expéditeur",
|
||||
message: "Message",
|
||||
response: "Réponse",
|
||||
at: "Envoyé à",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "Mode multi-utilisateurs",
|
||||
description:
|
||||
"Configurez votre instance pour prendre en charge votre équipe en activant le mode multi-utilisateurs.",
|
||||
enable: {
|
||||
"is-enable": "Le mode multi-utilisateurs est activé",
|
||||
enable: "Activer le mode multi-utilisateurs",
|
||||
description:
|
||||
"Par défaut, vous serez le seul administrateur. En tant qu'administrateur, vous devrez créer des comptes pour tous les nouveaux utilisateurs ou administrateurs. Ne perdez pas votre mot de passe car seul un utilisateur administrateur peut réinitialiser les mots de passe.",
|
||||
username: "Nom d'utilisateur du compte administrateur",
|
||||
password: "Mot de passe du compte administrateur",
|
||||
},
|
||||
password: {
|
||||
title: "Protection par mot de passe",
|
||||
description:
|
||||
"Protégez votre instance AnythingLLM avec un mot de passe. Si vous oubliez ce mot de passe, il n'y a pas de méthode de récupération, donc assurez-vous de le sauvegarder.",
|
||||
},
|
||||
instance: {
|
||||
title: "Protéger l'instance par mot de passe",
|
||||
description:
|
||||
"Par défaut, vous serez le seul administrateur. En tant qu'administrateur, vous devrez créer des comptes pour tous les nouveaux utilisateurs ou administrateurs. Ne perdez pas votre mot de passe car seul un utilisateur administrateur peut réinitialiser les mots de passe.",
|
||||
password: "Mot de passe de l'instance",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "Journaux d'événements",
|
||||
description:
|
||||
"Consultez toutes les actions et événements se produisant sur cette instance pour la surveillance.",
|
||||
clear: "Effacer les journaux d'événements",
|
||||
table: {
|
||||
type: "Type d'événement",
|
||||
user: "Utilisateur",
|
||||
occurred: "Survenu à",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "Confidentialité et gestion des données",
|
||||
description:
|
||||
"Voici votre configuration pour la gestion des données et des fournisseurs tiers connectés avec AnythingLLM.",
|
||||
llm: "Sélection LLM",
|
||||
embedding: "Préférence d'intégration",
|
||||
vector: "Base de données vectorielle",
|
||||
anonymous: "Télémétrie anonyme activée",
|
||||
},
|
||||
};
|
||||
|
||||
export default TRANSLATIONS;
|
32
frontend/src/locales/resources.js
Normal file
32
frontend/src/locales/resources.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Looking for a language to translate AnythingLLM to?
|
||||
// Create a `common.js` file in the language's ISO code https://www.w3.org/International/O-charset-lang.html
|
||||
// eg: Spanish => es/common.js
|
||||
// eg: French => fr/common.js
|
||||
// You should copy the en/common.js file as your template and just translate every string in there.
|
||||
// By default, we try to see what the browsers native language is set to and use that. If a string
|
||||
// is not defined or is null in the translation file, it will fallback to the value in the en/common.js file
|
||||
// RULES:
|
||||
// The EN translation file is the ground-truth for what keys and options are available. DO NOT add a special key
|
||||
// to a specific language file as this will break the other languages. Any new keys should be added to english
|
||||
// and the language file you are working on.
|
||||
|
||||
import English from "./en/common.js";
|
||||
import Spanish from "./es/common.js";
|
||||
import French from "./fr/common.js";
|
||||
import Mandarin from "./zh/common.js";
|
||||
|
||||
export const defaultNS = "common";
|
||||
export const resources = {
|
||||
en: {
|
||||
common: English,
|
||||
},
|
||||
zh: {
|
||||
common: Mandarin,
|
||||
},
|
||||
es: {
|
||||
common: Spanish,
|
||||
},
|
||||
fr: {
|
||||
common: French,
|
||||
},
|
||||
};
|
96
frontend/src/locales/verifyTranslations.mjs
Normal file
96
frontend/src/locales/verifyTranslations.mjs
Normal file
@ -0,0 +1,96 @@
|
||||
import { resources } from "./resources.js";
|
||||
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) {
|
||||
console.log("Invalid type comparison", [
|
||||
{
|
||||
lang,
|
||||
a: typeof a,
|
||||
b: typeof 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 verified: [${Object.keys(
|
||||
TRANSLATIONS
|
||||
).join(",")}]`
|
||||
);
|
||||
for (const [lang, translations] of Object.entries(TRANSLATIONS)) {
|
||||
const passed = compareStructures(lang, translations, PRIMARY);
|
||||
console.log(`${langDisplayName(lang)} (${lang}): ${passed ? "✅" : "❌"}`);
|
||||
!passed && failed.push(lang);
|
||||
}
|
||||
|
||||
if (failed.length !== 0)
|
||||
throw new Error(
|
||||
`The following translations files are INVALID and need fixing. Please see logs`,
|
||||
failed
|
||||
);
|
||||
console.log(
|
||||
`👍 All translation files located match the schema defined by the English file!`
|
||||
);
|
||||
process.exit(0);
|
424
frontend/src/locales/zh/common.js
Normal file
424
frontend/src/locales/zh/common.js
Normal file
@ -0,0 +1,424 @@
|
||||
// Anything with "null" requires a translation. Contribute to translation via a PR!
|
||||
const TRANSLATIONS = {
|
||||
common: {
|
||||
"workspaces-name": "工作区名称",
|
||||
error: "错误",
|
||||
success: "成功",
|
||||
user: "用户",
|
||||
selection: "模型选择",
|
||||
save: "保存更改",
|
||||
saving: "保存中...",
|
||||
previous: "上一页",
|
||||
next: "下一页",
|
||||
},
|
||||
|
||||
// Setting Sidebar menu items.
|
||||
settings: {
|
||||
title: "设置",
|
||||
system: "系统",
|
||||
invites: "邀请",
|
||||
users: "用户",
|
||||
workspaces: "工作区",
|
||||
"workspace-chats": "对话历史记录", // "workspace-chats" should be "对话历史记录", means "chat history",or "chat history records"
|
||||
appearance: "外观",
|
||||
"api-keys": "API 密钥",
|
||||
llm: "LLM 首选项",
|
||||
transcription: "Transcription 模型",
|
||||
embedder: "Embedder 首选项",
|
||||
"text-splitting": "文本分割",
|
||||
"vector-database": "向量数据库",
|
||||
embeds: "嵌入式对话",
|
||||
"embed-chats": "嵌入式对话历史",
|
||||
security: "用户与安全",
|
||||
"event-logs": "事件日志",
|
||||
privacy: "隐私与数据",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
login: {
|
||||
"multi-user": {
|
||||
welcome: "欢迎!",
|
||||
"placeholder-username": "请输入用户名",
|
||||
"placeholder-password": "请输入密码",
|
||||
login: "登录",
|
||||
validating: "登录",
|
||||
"forgot-pass": "忘记密码",
|
||||
reset: "重置",
|
||||
},
|
||||
"sign-in": {
|
||||
start: "登录你的",
|
||||
end: "账户",
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Settings menu items
|
||||
"workspaces—settings": {
|
||||
general: "通用设置",
|
||||
chat: "聊天设置",
|
||||
vector: "向量数据库",
|
||||
members: "成员",
|
||||
agent: "代理配置",
|
||||
},
|
||||
|
||||
// General Appearance
|
||||
general: {
|
||||
vector: {
|
||||
title: "向量数量",
|
||||
description: "向量数据库中的总向量数。",
|
||||
},
|
||||
names: {
|
||||
description: "这只会更改工作区的显示名称。",
|
||||
},
|
||||
message: {
|
||||
title: "建议的聊天消息",
|
||||
description: "自定义将向您的工作区用户建议的消息。",
|
||||
add: "添加新消息",
|
||||
save: "保存消息",
|
||||
heading: "向我解释",
|
||||
body: "AnythingLLM的好处",
|
||||
},
|
||||
pfp: {
|
||||
title: "助理头像",
|
||||
description: "为此工作区自定义助手的个人资料图像。",
|
||||
image: "工作区图像",
|
||||
remove: "移除工作区图像",
|
||||
},
|
||||
delete: {
|
||||
delete: "删除工作区",
|
||||
deleting: "正在删除工作区...",
|
||||
"confirm-start": "您即将删除整个",
|
||||
"confirm-end":
|
||||
"工作区。这将删除矢量数据库中的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。",
|
||||
},
|
||||
},
|
||||
|
||||
// Chat Settings
|
||||
chat: {
|
||||
llm: {
|
||||
title: "工作区LLM提供者",
|
||||
description:
|
||||
"将用于此工作区的特定 LLM 提供商和模型。默认情况下,它使用系统 LLM 提供程序和设置。",
|
||||
search: "搜索所有 LLM 提供商",
|
||||
},
|
||||
model: {
|
||||
title: "工作区聊天模型",
|
||||
description:
|
||||
"将用于此工作区的特定聊天模型。如果为空,将使用系统LLM首选项。",
|
||||
wait: "-- 等待模型 --",
|
||||
},
|
||||
mode: {
|
||||
title: "聊天模式",
|
||||
chat: {
|
||||
title: "聊天",
|
||||
"desc-start": "将提供法学硕士的一般知识",
|
||||
and: "和",
|
||||
"desc-end": "找到的文档上下文的答案。",
|
||||
},
|
||||
query: {
|
||||
title: "查询",
|
||||
"desc-start": "将",
|
||||
only: "仅",
|
||||
"desc-end": "提供找到的文档上下文的答案。",
|
||||
},
|
||||
},
|
||||
history: {
|
||||
title: "聊天历史记录",
|
||||
"desc-start": "将包含在响应的短期记忆中的先前聊天的数量。",
|
||||
recommend: "推荐 20。",
|
||||
"desc-end":
|
||||
"任何超过 45 的值都可能导致连续聊天失败,具体取决于消息大小。",
|
||||
},
|
||||
prompt: {
|
||||
title: "聊天提示",
|
||||
description:
|
||||
"将在此工作区上使用的提示。定义 AI 生成响应的上下文和指令。您应该提供精心设计的提示,以便人工智能可以生成相关且准确的响应。",
|
||||
},
|
||||
refusal: {
|
||||
title: "查询模式拒绝响应",
|
||||
"desc-start": "当处于",
|
||||
query: "查询",
|
||||
"desc-end": "模式时,当未找到上下文时,您可能希望返回自定义拒绝响应。",
|
||||
},
|
||||
temperature: {
|
||||
title: "LLM Temperature",
|
||||
"desc-start": "此设置控制您的 LLM 回答的“创意”程度",
|
||||
"desc-end":
|
||||
"数字越高越有创意。对于某些模型,如果设置得太高,可能会导致响应不一致。",
|
||||
hint: "大多数法学硕士都有各种可接受的有效值范围。请咨询您的法学硕士提供商以获取该信息。",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database Settings
|
||||
"vector-workspace": {
|
||||
identifier: "向量数据库标识符",
|
||||
snippets: {
|
||||
title: "最大上下文片段",
|
||||
description:
|
||||
"此设置控制每次聊天或查询将发送到 LLM 的上下文片段的最大数量。",
|
||||
recommend: "推荐: 4",
|
||||
},
|
||||
doc: {
|
||||
title: "文档相似性阈值",
|
||||
description:
|
||||
"源被视为与聊天相关所需的最低相似度分数。数字越高,来源与聊天就越相似。",
|
||||
zero: "无限制",
|
||||
low: "低(相似度分数 ≥ .25)",
|
||||
medium: "中(相似度分数 ≥ .50)",
|
||||
high: "高(相似度分数 ≥ .75)",
|
||||
},
|
||||
reset: {
|
||||
reset: "重置向量数据库",
|
||||
resetting: "清除向量...",
|
||||
confirm:
|
||||
"您将重置此工作区的矢量数据库。这将删除当前嵌入的所有矢量嵌入。\n\n原始源文件将保持不变。此操作是不可逆转的。",
|
||||
success: "向量数据库已重置。",
|
||||
error: "无法重置工作区向量数据库!",
|
||||
},
|
||||
},
|
||||
|
||||
// Agent Configuration
|
||||
agent: {
|
||||
"performance-warning":
|
||||
"不明确支持工具调用的 LLMs 的性能高度依赖于模型的功能和准确性。有些能力可能受到限制或不起作用。",
|
||||
provider: {
|
||||
title: "工作区代理 LLM 提供商",
|
||||
description: "将用于此工作区的 @agent 代理的特定 LLM 提供商和模型。",
|
||||
},
|
||||
mode: {
|
||||
chat: {
|
||||
title: "工作区代理聊天模型",
|
||||
description: "将用于此工作区的 @agent 代理的特定聊天模型。",
|
||||
},
|
||||
title: "工作区代理模型",
|
||||
description: "将用于此工作区的 @agent 代理的特定 LLM 模型。",
|
||||
wait: "-- 等待模型 --",
|
||||
},
|
||||
skill: {
|
||||
title: "默认代理技能",
|
||||
description:
|
||||
"使用这些预构建的技能提高默认代理的自然能力。此设置适用于所有工作区。",
|
||||
rag: {
|
||||
title: "RAG和长期记忆",
|
||||
description:
|
||||
'允许代理利用您的本地文档来回答查询,或要求代理"记住"长期记忆检索的内容片段。',
|
||||
},
|
||||
view: {
|
||||
title: "查看和总结文档",
|
||||
description: "允许代理列出和总结当前嵌入的工作区文件的内容。",
|
||||
},
|
||||
scrape: {
|
||||
title: "抓取网站",
|
||||
description: "允许代理访问和抓取网站的内容。",
|
||||
},
|
||||
generate: {
|
||||
title: "生成图表",
|
||||
description: "使默认代理能够从提供的数据或聊天中生成各种类型的图表。",
|
||||
},
|
||||
save: {
|
||||
title: "生成并保存文件到浏览器",
|
||||
description:
|
||||
"使默认代理能够生成并写入文件,这些文件可以保存并在您的浏览器中下载。",
|
||||
},
|
||||
web: {
|
||||
title: "实时网络搜索和浏览",
|
||||
"desc-start":
|
||||
"通过连接到网络搜索(SERP)提供者,使您的代理能够搜索网络以回答您的问题。",
|
||||
"desc-end": "在代理会话期间,网络搜索将不起作用,直到此设置完成。",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Workspace Chat
|
||||
recorded: {
|
||||
title: "工作区聊天历史记录",
|
||||
description: "这些是用户发送的所有聊天记录和消息,按创建日期排序。",
|
||||
export: "导出",
|
||||
table: {
|
||||
id: "Id",
|
||||
by: "Sent By",
|
||||
workspace: "Workspace",
|
||||
prompt: "Prompt",
|
||||
response: "Response",
|
||||
at: "Sent At",
|
||||
},
|
||||
},
|
||||
|
||||
appearance: {
|
||||
title: "外观",
|
||||
description: "自定义平台的外观设置。",
|
||||
logo: {
|
||||
title: "自定义图标",
|
||||
description: "上传您的自定义图标,让您的聊天机器人成为您的。",
|
||||
add: "添加自定义图标",
|
||||
recommended: "建议尺寸:800 x 200",
|
||||
remove: "移除",
|
||||
replace: "替换",
|
||||
},
|
||||
message: {
|
||||
title: "自定义消息",
|
||||
description: "自定义向用户显示的自动消息。",
|
||||
new: "新建",
|
||||
system: "系统",
|
||||
user: "用户",
|
||||
message: "消息",
|
||||
assistant: "AnythingLLM 聊天助手",
|
||||
"double-click": "双击以编辑...",
|
||||
save: "保存消息",
|
||||
},
|
||||
icons: {
|
||||
title: "自定义页脚图标",
|
||||
description: "自定义侧边栏底部显示的页脚图标。",
|
||||
icon: "图标",
|
||||
link: "链接",
|
||||
},
|
||||
},
|
||||
|
||||
// API Keys
|
||||
api: {
|
||||
title: "API 密钥",
|
||||
description: "API 密钥允许持有者以编程方式访问和管理此 AnythingLLM 实例。",
|
||||
link: "阅读 API 文档",
|
||||
generate: "生成新的 API 密钥",
|
||||
table: {
|
||||
key: "API 密钥",
|
||||
by: "创建者",
|
||||
created: "创建",
|
||||
},
|
||||
},
|
||||
|
||||
// LLM Preferences
|
||||
llm: {
|
||||
title: "LLM 偏好",
|
||||
description:
|
||||
"这些是您首选的 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 时,您可能需要额外指定用于嵌入文本的凭据。",
|
||||
"desc-end":
|
||||
"嵌入是将文本转换为矢量的过程。需要这些凭据才能将您的文件和提示转换为 AnythingLLM 可以用来处理的格式。",
|
||||
provider: {
|
||||
title: "嵌入引擎提供商",
|
||||
description: "使用 AnythingLLM 的本机嵌入引擎时不需要设置。",
|
||||
},
|
||||
},
|
||||
|
||||
text: {
|
||||
title: "文本拆分和分块首选项",
|
||||
"desc-start":
|
||||
"有时,您可能希望更改新文档在插入到矢量数据库之前拆分和分块的默认方式。",
|
||||
"desc-end": "只有在了解文本拆分的工作原理及其副作用时,才应修改此设置。",
|
||||
"warn-start": "此处的更改仅适用于",
|
||||
"warn-center": "新嵌入的文档",
|
||||
"warn-end": ",而不是现有文档。",
|
||||
size: {
|
||||
title: "文本块大小",
|
||||
description: "这是单个向量中可以存在的字符的最大长度。",
|
||||
recommend: "嵌入模型的最大长度为",
|
||||
},
|
||||
overlap: {
|
||||
title: "文本块重叠",
|
||||
description: "这是在两个相邻文本块之间分块期间发生的最大字符重叠。",
|
||||
},
|
||||
},
|
||||
|
||||
// Vector Database
|
||||
vector: {
|
||||
title: "向量数据库",
|
||||
description:
|
||||
"这些是 AnythingLLM 实例如何运行的凭据和设置。重要的是,这些密钥是最新的和正确的。",
|
||||
provider: {
|
||||
title: "向量数据库提供商",
|
||||
description: "LanceDB 不需要任何配置。",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chats
|
||||
embeddable: {
|
||||
title: "可嵌入的聊天小部件",
|
||||
description:
|
||||
"可嵌入的聊天小部件是与单个工作区绑定的面向公众的聊天界面。这些允许您构建工作区,然后您可以将其发布到全世界。",
|
||||
create: "创建嵌入式对话",
|
||||
table: {
|
||||
workspace: "工作区",
|
||||
chats: "已发送聊天",
|
||||
Active: "活动域",
|
||||
},
|
||||
},
|
||||
|
||||
// Embeddable Chat History
|
||||
"embed-chats": {
|
||||
title: "嵌入聊天",
|
||||
description: "这些是您发布的任何嵌入的所有记录的聊天和消息。",
|
||||
table: {
|
||||
embed: "嵌入",
|
||||
sender: "发送者",
|
||||
message: "消息",
|
||||
response: "响应",
|
||||
at: "发送于",
|
||||
},
|
||||
},
|
||||
|
||||
multi: {
|
||||
title: "多用户模式",
|
||||
description: "通过激活多用户模式来设置您的实例以支持您的团队。",
|
||||
enable: {
|
||||
"is-enable": "多用户模式已启用",
|
||||
enable: "启用多用户模式",
|
||||
description:
|
||||
"默认情况下,您将是唯一的管理员。作为管理员,您需要为所有新用户或管理员创建账户。不要丢失您的密码,因为只有管理员用户可以重置密码。",
|
||||
username: "管理员账户用户名",
|
||||
password: "管理员账户密码",
|
||||
},
|
||||
password: {
|
||||
title: "密码保护",
|
||||
description:
|
||||
"用密码保护您的AnythingLLM实例。如果您忘记了密码,那么没有恢复方法,所以请确保保存这个密码。",
|
||||
},
|
||||
instance: {
|
||||
title: "实例密码保护",
|
||||
description:
|
||||
"默认情况下,您将是唯一的管理员。作为管理员,您需要为所有新用户或管理员创建账户。不要丢失您的密码,因为只有管理员用户可以重置密码。",
|
||||
password: "实例密码",
|
||||
},
|
||||
},
|
||||
|
||||
// Event Logs
|
||||
event: {
|
||||
title: "事件日志",
|
||||
description: "查看此实例上发生的所有操作和事件以进行监控。",
|
||||
clear: "清除事件日志",
|
||||
table: {
|
||||
type: "事件类型",
|
||||
user: "用户",
|
||||
occurred: "发生时间",
|
||||
},
|
||||
},
|
||||
|
||||
// Privacy & Data-Handling
|
||||
privacy: {
|
||||
title: "隐私和数据处理",
|
||||
description:
|
||||
"这是您对如何处理连接的第三方提供商和AnythingLLM的数据的配置。",
|
||||
llm: "LLM选择",
|
||||
embedding: "嵌入偏好",
|
||||
vector: "向量数据库",
|
||||
anonymous: "启用匿名遥测",
|
||||
},
|
||||
};
|
||||
|
||||
export default TRANSLATIONS;
|
@ -7,6 +7,7 @@ import * as Skeleton from "react-loading-skeleton";
|
||||
import LogRow from "./LogRow";
|
||||
import showToast from "@/utils/toast";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AdminLogs() {
|
||||
const query = useQuery();
|
||||
@ -14,6 +15,7 @@ export default function AdminLogs() {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
||||
const [canNext, setCanNext] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchLogs() {
|
||||
@ -62,12 +64,11 @@ export default function AdminLogs() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Event Logs
|
||||
{t("event.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
View all actions and events happening on this instance for
|
||||
monitoring.
|
||||
{t("event.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
@ -75,7 +76,7 @@ export default function AdminLogs() {
|
||||
onClick={handleResetLogs}
|
||||
className="mt-3 mr-0 -mb-14 z-10"
|
||||
>
|
||||
Clear Event Logs
|
||||
{t("event.clear")}
|
||||
</CTAButton>
|
||||
</div>
|
||||
<LogsContainer
|
||||
@ -100,6 +101,7 @@ function LogsContainer({
|
||||
handleNext,
|
||||
handlePrevious,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (loading) {
|
||||
return (
|
||||
<Skeleton.default
|
||||
@ -120,13 +122,13 @@ function LogsContainer({
|
||||
<thead className="text-white text-opacity-80 text-xs leading-[18px] font-bold uppercase border-white border-b border-opacity-60">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||
Event Type
|
||||
{t("event.table.type")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
User
|
||||
{t("event.table.user")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Occurred At
|
||||
{t("event.table.occurred")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||
{" "}
|
||||
@ -143,14 +145,14 @@ function LogsContainer({
|
||||
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible"
|
||||
disabled={offset === 0}
|
||||
>
|
||||
Previous Page
|
||||
{t("common.previous")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible"
|
||||
disabled={!canNext}
|
||||
>
|
||||
Next Page
|
||||
{t("common.next")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import Admin from "@/models/admin";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function NewWorkspaceModal({ closeModal }) {
|
||||
const [error, setError] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
const handleCreate = async (e) => {
|
||||
setError(null);
|
||||
e.preventDefault();
|
||||
@ -37,7 +39,7 @@ export default function NewWorkspaceModal({ closeModal }) {
|
||||
htmlFor="name"
|
||||
className="block mb-2 text-sm font-medium text-white"
|
||||
>
|
||||
Workspace name
|
||||
{t("common.workspaces-name")}
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
|
@ -13,10 +13,11 @@ import System from "@/models/system";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function AdminApiKeys() {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -27,11 +28,12 @@ export default function AdminApiKeys() {
|
||||
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-white">API Keys</p>
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
{t("api.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
API keys allow the holder to programmatically access and manage
|
||||
this AnythingLLM instance.
|
||||
{t("api.description")}
|
||||
</p>
|
||||
<a
|
||||
href={paths.apiDocs()}
|
||||
@ -39,13 +41,13 @@ export default function AdminApiKeys() {
|
||||
rel="noreferrer"
|
||||
className="text-xs leading-[18px] font-base text-blue-300 hover:underline"
|
||||
>
|
||||
Read the API documentation →
|
||||
{t("api.link")} →
|
||||
</a>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
<CTAButton onClick={openModal} className="mt-3 mr-0 -mb-14 z-10">
|
||||
<PlusCircle className="h-4 w-4" weight="bold" /> Generate New API
|
||||
Key
|
||||
<PlusCircle className="h-4 w-4" weight="bold" />{" "}
|
||||
{t("api.generate")}
|
||||
</CTAButton>
|
||||
</div>
|
||||
<ApiKeysContainer />
|
||||
@ -61,6 +63,7 @@ export default function AdminApiKeys() {
|
||||
function ApiKeysContainer() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [apiKeys, setApiKeys] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchExistingKeys() {
|
||||
@ -92,13 +95,13 @@ function ApiKeysContainer() {
|
||||
<thead className="text-white text-opacity-80 text-xs leading-[18px] font-bold uppercase border-white border-b border-opacity-60">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||
API Key
|
||||
{t("api.table.key")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Created By
|
||||
{t("api.table.by")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Created
|
||||
{t("api.table.created")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||
{" "}
|
||||
|
@ -3,6 +3,7 @@ import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function CustomLogo() {
|
||||
const { logo: _initLogo, setLogo: _setLogo } = useLogo();
|
||||
@ -65,15 +66,16 @@ export default function CustomLogo() {
|
||||
const triggerFileInputClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="mt-6 mb-8">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-base leading-6 font-bold text-white">
|
||||
Custom Logo
|
||||
{t("appearance.logo.title")}
|
||||
</h2>
|
||||
<p className="text-xs leading-[18px] font-base text-white/60">
|
||||
Upload your custom logo to make your chatbot yours.
|
||||
{t("appearance.logo.description")}
|
||||
</p>
|
||||
</div>
|
||||
{isDefaultLogo ? (
|
||||
@ -99,10 +101,10 @@ export default function CustomLogo() {
|
||||
<Plus className="w-6 h-6 text-black/80 m-2" />
|
||||
</div>
|
||||
<div className="text-white text-opacity-80 text-sm font-semibold py-1">
|
||||
Add a custom logo
|
||||
{t("appearance.logo.add")}
|
||||
</div>
|
||||
<div className="text-white text-opacity-60 text-xs font-medium py-1">
|
||||
Recommended size: 800 x 200
|
||||
{t("appearance.logo.recommended")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -123,7 +125,7 @@ export default function CustomLogo() {
|
||||
onClick={triggerFileInputClick}
|
||||
className="text-white text-base font-medium hover:text-opacity-60 mx-2"
|
||||
>
|
||||
Replace
|
||||
{t("appearance.logo.replace")}
|
||||
</button>
|
||||
|
||||
<input
|
||||
@ -138,7 +140,7 @@ export default function CustomLogo() {
|
||||
onClick={handleRemoveLogo}
|
||||
className="text-white text-base font-medium hover:text-opacity-60 mx-2"
|
||||
>
|
||||
Remove
|
||||
{t("appearance.logo.remove")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,10 +3,12 @@ import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function CustomMessages() {
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [messages, setMessages] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMessages() {
|
||||
@ -20,12 +22,12 @@ export default function CustomMessages() {
|
||||
if (type === "user") {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "Double click to edit...", response: "" },
|
||||
{ user: t("appearance.message.double-click"), response: "" },
|
||||
]);
|
||||
} else {
|
||||
setMessages([
|
||||
...messages,
|
||||
{ user: "", response: "Double click to edit..." },
|
||||
{ user: "", response: t("appearance.message.double-click") },
|
||||
]);
|
||||
}
|
||||
};
|
||||
@ -56,10 +58,10 @@ export default function CustomMessages() {
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-base leading-6 font-bold text-white">
|
||||
Custom Messages
|
||||
{t("appearance.message.title")}
|
||||
</h2>
|
||||
<p className="text-xs leading-[18px] font-base text-white/60">
|
||||
Customize the automatic messages displayed to your users.
|
||||
{t("appearance.message.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-col gap-y-6 bg-dark-highlight rounded-lg pr-[31px] pl-[12px] pt-4 max-w-[700px]">
|
||||
@ -93,8 +95,11 @@ export default function CustomMessages() {
|
||||
<div className="flex items-center justify-start text-sm font-normal -ml-2">
|
||||
<Plus className="m-2" size={16} weight="bold" />
|
||||
<span className="leading-5">
|
||||
New <span className="font-bold italic mr-1">system</span>{" "}
|
||||
message
|
||||
{t("appearance.message.new")}{" "}
|
||||
<span className="font-bold italic mr-1">
|
||||
{t("appearance.message.system")}
|
||||
</span>{" "}
|
||||
{t("appearance.message.message")}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@ -105,7 +110,11 @@ export default function CustomMessages() {
|
||||
<div className="flex items-center justify-start text-sm font-normal">
|
||||
<Plus className="m-2" size={16} weight="bold" />
|
||||
<span className="leading-5">
|
||||
New <span className="font-bold italic mr-1">user</span> message
|
||||
{t("appearance.message.new")}{" "}
|
||||
<span className="font-bold italic mr-1">
|
||||
{t("appearance.message.user")}
|
||||
</span>{" "}
|
||||
{t("appearance.message.message")}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@ -117,7 +126,7 @@ export default function CustomMessages() {
|
||||
className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
|
||||
onClick={handleMessageSave}
|
||||
>
|
||||
Save Messages
|
||||
{t("appearance.message.save")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
@ -4,10 +4,11 @@ import { safeJsonParse } from "@/utils/request";
|
||||
import NewIconForm from "./NewIconForm";
|
||||
import Admin from "@/models/admin";
|
||||
import System from "@/models/system";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function FooterCustomization() {
|
||||
const [footerIcons, setFooterIcons] = useState(Array(3).fill(null));
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
async function fetchFooterIcons() {
|
||||
const settings = (await Admin.systemPreferences())?.settings;
|
||||
@ -52,15 +53,15 @@ export default function FooterCustomization() {
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-base leading-6 font-bold text-white">
|
||||
Custom Footer Icons
|
||||
{t("appearance.icons.title")}
|
||||
</h2>
|
||||
<p className="text-xs leading-[18px] font-base text-white/60">
|
||||
Customize the footer icons displayed on the bottom of the sidebar.
|
||||
{t("appearance.icons.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 flex gap-x-3 font-bold text-white text-sm">
|
||||
<div>Icon</div>
|
||||
<div>Link</div>
|
||||
<div>{t("appearance.icons.icon")}</div>
|
||||
<div>{t("appearance.icons.link")}</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col gap-y-[10px]">
|
||||
{footerIcons.map((icon, index) => (
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { useLanguageOptions } from "@/hooks/useLanguageOptions";
|
||||
|
||||
export default function LanguagePreference() {
|
||||
const {
|
||||
currentLanguage,
|
||||
supportedLanguages,
|
||||
getLanguageName,
|
||||
changeLanguage,
|
||||
} = useLanguageOptions();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-base leading-6 font-bold text-white">
|
||||
Display Language
|
||||
</h2>
|
||||
<p className="text-xs leading-[18px] font-base text-white/60">
|
||||
Select the preferred language to render AnythingLLM's UI in, when
|
||||
applicable.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<select
|
||||
name="userLang"
|
||||
className="bg-zinc-900 w-fit mt-2 px-4 border-gray-500 text-white text-sm rounded-lg block py-2"
|
||||
defaultValue={currentLanguage || "en"}
|
||||
onChange={(e) => changeLanguage(e.target.value)}
|
||||
>
|
||||
{supportedLanguages.map((lang) => {
|
||||
return (
|
||||
<option key={lang} value={lang}>
|
||||
{getLanguageName(lang)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -4,9 +4,12 @@ import FooterCustomization from "./FooterCustomization";
|
||||
import SupportEmail from "./SupportEmail";
|
||||
import CustomLogo from "./CustomLogo";
|
||||
import CustomMessages from "./CustomMessages";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CustomAppName from "./CustomAppName";
|
||||
import LanguagePreference from "./LanguagePreference";
|
||||
|
||||
export default function Appearance() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -18,13 +21,14 @@ export default function Appearance() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Appearance
|
||||
{t("appearance.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
Customize the appearance settings of your platform.
|
||||
{t("appearance.description")}
|
||||
</p>
|
||||
</div>
|
||||
<LanguagePreference />
|
||||
<CustomLogo />
|
||||
<CustomAppName />
|
||||
<CustomMessages />
|
||||
|
@ -9,6 +9,7 @@ import showToast from "@/utils/toast";
|
||||
import System from "@/models/system";
|
||||
import { CaretDown, Download, Trash } from "@phosphor-icons/react";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const exportOptions = {
|
||||
csv: {
|
||||
@ -54,6 +55,7 @@ export default function WorkspaceChats() {
|
||||
const [chats, setChats] = useState([]);
|
||||
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
||||
const [canNext, setCanNext] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDumpChats = async (exportType) => {
|
||||
const chats = await System.exportChats(exportType);
|
||||
@ -122,7 +124,7 @@ export default function WorkspaceChats() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Workspace Chats
|
||||
{t("recorded.title")}
|
||||
</p>
|
||||
<div className="relative">
|
||||
<button
|
||||
@ -131,7 +133,7 @@ export default function WorkspaceChats() {
|
||||
className="flex items-center gap-x-2 px-4 py-1 rounded-lg bg-primary-button hover:text-white text-xs font-semibold hover:bg-secondary shadow-[0_4px_14px_rgba(0,0,0,0.25)] h-[34px] w-fit"
|
||||
>
|
||||
<Download size={18} weight="bold" />
|
||||
Export
|
||||
{t("recorded.export")}
|
||||
<CaretDown size={18} weight="bold" />
|
||||
</button>
|
||||
<div
|
||||
@ -167,8 +169,7 @@ export default function WorkspaceChats() {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
These are all the recorded chats and messages that have been sent
|
||||
by users ordered by their creation date.
|
||||
{t("recorded.description")}
|
||||
</p>
|
||||
</div>
|
||||
<ChatsContainer
|
||||
@ -178,6 +179,7 @@ export default function WorkspaceChats() {
|
||||
offset={offset}
|
||||
setOffset={setOffset}
|
||||
canNext={canNext}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -192,6 +194,7 @@ function ChatsContainer({
|
||||
offset,
|
||||
setOffset,
|
||||
canNext,
|
||||
t,
|
||||
}) {
|
||||
const handlePrevious = () => {
|
||||
setOffset(Math.max(offset - 1, 0));
|
||||
@ -225,22 +228,22 @@ function ChatsContainer({
|
||||
<thead className="text-white text-opacity-80 text-xs leading-[18px] font-bold uppercase border-white border-b border-opacity-60">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||
Id
|
||||
{t("recorded.table.id")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Sent By
|
||||
{t("recorded.table.by")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Workspace
|
||||
{t("recorded.table.workspace")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Prompt
|
||||
{t("recorded.table.prompt")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Response
|
||||
{t("recorded.table.response")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Sent At
|
||||
{t("recorded.table.at")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||
{" "}
|
||||
|
@ -6,9 +6,11 @@ import "react-loading-skeleton/dist/skeleton.css";
|
||||
import useQuery from "@/hooks/useQuery";
|
||||
import ChatRow from "./ChatRow";
|
||||
import Embed from "@/models/embed";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function EmbedChats() {
|
||||
// TODO [FEAT]: Add export of embed chats
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -20,12 +22,11 @@ export default function EmbedChats() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Embed Chats
|
||||
{t("embed-chats.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
These are all the recorded chats and messages from any embed that
|
||||
you have published.
|
||||
{t("embed-chats.description")}
|
||||
</p>
|
||||
</div>
|
||||
<ChatsContainer />
|
||||
@ -41,6 +42,7 @@ function ChatsContainer() {
|
||||
const [chats, setChats] = useState([]);
|
||||
const [offset, setOffset] = useState(Number(query.get("offset") || 0));
|
||||
const [canNext, setCanNext] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handlePrevious = () => {
|
||||
setOffset(Math.max(offset - 1, 0));
|
||||
@ -83,19 +85,19 @@ function ChatsContainer() {
|
||||
<thead className="text-white text-opacity-80 text-sm font-bold uppercase border-white border-b border-opacity-60">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||
Embed
|
||||
{t("embed-chats.table.embed")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Sender
|
||||
{t("embed-chats.table.sender")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Message
|
||||
{t("embed-chats.table.message")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Response
|
||||
{t("embed-chats.table.response")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Sent At
|
||||
{t("embed-chats.table.at")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||
{" "}
|
||||
@ -116,14 +118,14 @@ function ChatsContainer() {
|
||||
disabled={offset === 0}
|
||||
>
|
||||
{" "}
|
||||
Previous Page
|
||||
{t("common.previous")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
className="px-4 py-2 rounded-lg border border-slate-200 text-slate-200 text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 disabled:invisible"
|
||||
disabled={!canNext}
|
||||
>
|
||||
Next Page
|
||||
{t("common.next")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import * as Skeleton from "react-loading-skeleton";
|
||||
@ -13,7 +14,7 @@ import CTAButton from "@/components/lib/CTAButton";
|
||||
|
||||
export default function EmbedConfigs() {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-sidebar flex">
|
||||
<Sidebar />
|
||||
@ -25,18 +26,17 @@ export default function EmbedConfigs() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Embeddable Chat Widgets
|
||||
{t("embeddable.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
Embeddable chat widgets are public facing chat interfaces that are
|
||||
tied to a single workspace. These allow you to build workspaces
|
||||
that then you can publish to the world.
|
||||
{t("embeddable.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
<CTAButton onClick={openModal} className="mt-3 mr-0 -mb-14 z-10">
|
||||
<CodeBlock className="h-4 w-4" weight="bold" /> Create embed
|
||||
<CodeBlock className="h-4 w-4" weight="bold" />{" "}
|
||||
{t("embeddable.create")}
|
||||
</CTAButton>
|
||||
</div>
|
||||
<EmbedContainer />
|
||||
@ -52,6 +52,7 @@ export default function EmbedConfigs() {
|
||||
function EmbedContainer() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [embeds, setEmbeds] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchUsers() {
|
||||
@ -81,13 +82,13 @@ function EmbedContainer() {
|
||||
<thead className="text-white text-opacity-80 text-xs leading-[18px] font-bold uppercase border-white border-b border-opacity-60">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 rounded-tl-lg">
|
||||
Workspace
|
||||
{t("embeddable.table.workspace")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Sent Chats
|
||||
{t("embeddable.table.chats")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Active Domains
|
||||
{t("embeddable.table.Active")}
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 rounded-tr-lg">
|
||||
{" "}
|
||||
|
@ -30,6 +30,7 @@ import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const EMBEDDERS = [
|
||||
{
|
||||
@ -112,6 +113,7 @@ export default function GeneralEmbeddingPreference() {
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const { t } = useTranslation();
|
||||
|
||||
function embedderModelChanged(formEl) {
|
||||
try {
|
||||
@ -223,17 +225,13 @@ export default function GeneralEmbeddingPreference() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Embedding Preference
|
||||
{t("embedding.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
When using an LLM that does not natively support an embedding
|
||||
engine - you may need to additionally specify credentials to
|
||||
for embedding text.
|
||||
{t("embedding.desc-start")}
|
||||
<br />
|
||||
Embedding is the process of turning text into vectors. These
|
||||
credentials are required to turn your files and prompts into a
|
||||
format which AnythingLLM can use to process.
|
||||
{t("embedding.desc-end")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
@ -242,12 +240,12 @@ export default function GeneralEmbeddingPreference() {
|
||||
onClick={() => handleSubmit()}
|
||||
className="mt-3 mr-0 -mb-14 z-10"
|
||||
>
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</CTAButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Embedding Provider
|
||||
{t("embedding.provider.title")}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
|
@ -6,6 +6,7 @@ import CTAButton from "@/components/lib/CTAButton";
|
||||
import Admin from "@/models/admin";
|
||||
import showToast from "@/utils/toast";
|
||||
import { nFormatter, numberWithCommas } from "@/utils/numbers";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function isNullOrNaN(value) {
|
||||
if (value === null) return true;
|
||||
@ -17,6 +18,7 @@ export default function EmbeddingTextSplitterPreference() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -86,25 +88,22 @@ export default function EmbeddingTextSplitterPreference() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-4 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Text splitting & Chunking Preferences
|
||||
{t("text.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
Sometimes, you may want to change the default way that new
|
||||
documents are split and chunked before being inserted into
|
||||
your vector database. <br />
|
||||
You should only modify this setting if you understand how text
|
||||
splitting works and it's side effects.
|
||||
{t("text.desc-start")} <br />
|
||||
{t("text.desc-end")}
|
||||
</p>
|
||||
<p className="text-xs leading-[18px] font-semibold text-white/80">
|
||||
Changes here will only apply to{" "}
|
||||
<i>newly embedded documents</i>, not existing documents.
|
||||
{t("text.warn-start")} <i>{t("text.warn-center")}</i>
|
||||
{t("text.warn-end")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
{hasChanges && (
|
||||
<CTAButton className="mt-3 mr-0 -mb-14 z-10">
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</CTAButton>
|
||||
)}
|
||||
</div>
|
||||
@ -113,11 +112,10 @@ export default function EmbeddingTextSplitterPreference() {
|
||||
<div className="flex flex-col max-w-[300px]">
|
||||
<div className="flex flex-col gap-y-2 mb-4">
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Text Chunk Size
|
||||
{t("text.size.title")}
|
||||
</label>
|
||||
<p className="text-xs text-white/60">
|
||||
This is the maximum length of characters that can be
|
||||
present in a single vector.
|
||||
{t("text.size.description")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
@ -137,7 +135,7 @@ export default function EmbeddingTextSplitterPreference() {
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="text-xs text-white/40">
|
||||
Embed model maximum length is{" "}
|
||||
{t("text.size.recommend")}{" "}
|
||||
{numberWithCommas(settings?.max_embed_chunk_size || 1000)}.
|
||||
</p>
|
||||
</div>
|
||||
@ -147,11 +145,10 @@ export default function EmbeddingTextSplitterPreference() {
|
||||
<div className="flex flex-col max-w-[300px]">
|
||||
<div className="flex flex-col gap-y-2 mb-4">
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Text Chunk Overlap
|
||||
{t("text.overlap.title")}
|
||||
</label>
|
||||
<p className="text-xs text-white/60">
|
||||
This is the maximum overlap of characters that occurs
|
||||
during chunking between two adjacent text chunks.
|
||||
{t("text.overlap.description")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Sidebar from "@/components/SettingsSidebar";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import System from "@/models/system";
|
||||
@ -232,6 +233,7 @@ export default function GeneralLLMPreference() {
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const isHosted = window.location.hostname.includes("useanything.com");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -310,14 +312,11 @@ export default function GeneralLLMPreference() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
LLM Preference
|
||||
{t("llm.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
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.
|
||||
{t("llm.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
@ -331,7 +330,7 @@ export default function GeneralLLMPreference() {
|
||||
)}
|
||||
</div>
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
LLM Provider
|
||||
{t("llm.provider")}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
|
@ -9,11 +9,12 @@ import {
|
||||
LLM_SELECTION_PRIVACY,
|
||||
VECTOR_DB_PRIVACY,
|
||||
} from "@/pages/OnboardingFlow/Steps/DataHandling";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function PrivacyAndDataHandling() {
|
||||
const [settings, setSettings] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
async function fetchSettings() {
|
||||
setLoading(true);
|
||||
@ -35,12 +36,11 @@ export default function PrivacyAndDataHandling() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Privacy & Data-Handling
|
||||
{t("privacy.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
This is your configuration for how connected third party providers
|
||||
and AnythingLLM handle your data.
|
||||
{t("privacy.description")}
|
||||
</p>
|
||||
</div>
|
||||
{loading ? (
|
||||
@ -65,12 +65,15 @@ function ThirdParty({ settings }) {
|
||||
const llmChoice = settings?.LLMProvider || "openai";
|
||||
const embeddingEngine = settings?.EmbeddingEngine || "openai";
|
||||
const vectorDb = settings?.VectorDB || "lancedb";
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="py-8 w-full flex items-start justify-center flex-col gap-y-6 border-b-2 border-white/10">
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-y-2 border-b border-zinc-500/50 pb-4">
|
||||
<div className="text-white text-base font-bold">LLM Selection</div>
|
||||
<div className="text-white text-base font-bold">
|
||||
{t("privacy.llm")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={LLM_SELECTION_PRIVACY[llmChoice].logo}
|
||||
@ -89,7 +92,7 @@ function ThirdParty({ settings }) {
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-2 border-b border-zinc-500/50 pb-4">
|
||||
<div className="text-white text-base font-bold">
|
||||
Embedding Preference
|
||||
{t("privacy.embedding")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
@ -111,7 +114,9 @@ function ThirdParty({ settings }) {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-2 pb-4">
|
||||
<div className="text-white text-base font-bold">Vector Database</div>
|
||||
<div className="text-white text-base font-bold">
|
||||
{t("privacy.vector")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={VECTOR_DB_PRIVACY[vectorDb].logo}
|
||||
@ -137,6 +142,7 @@ function TelemetryLogs({ settings }) {
|
||||
const [telemetry, setTelemetry] = useState(
|
||||
settings?.DisableTelemetry !== "true"
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
async function toggleTelemetry() {
|
||||
await System.updateSystem({
|
||||
DisableTelemetry: !telemetry ? "false" : "true",
|
||||
@ -157,7 +163,7 @@ function TelemetryLogs({ settings }) {
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="">
|
||||
<label className="mb-2.5 block font-medium text-white">
|
||||
Anonymous Telemetry Enabled
|
||||
{t("privacy.anonymous")}
|
||||
</label>
|
||||
<label className="relative inline-flex cursor-pointer items-center">
|
||||
<input
|
||||
|
@ -7,6 +7,7 @@ import paths from "@/utils/paths";
|
||||
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function GeneralSecurity() {
|
||||
return (
|
||||
@ -29,6 +30,7 @@ function MultiUserMode() {
|
||||
const [useMultiUserMode, setUseMultiUserMode] = useState(false);
|
||||
const [multiUserModeEnabled, setMultiUserModeEnabled] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -90,12 +92,11 @@ function MultiUserMode() {
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Multi-User Mode
|
||||
{t("multi.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
Set up your instance to support your team by activating Multi-User
|
||||
Mode.
|
||||
{t("multi.description")}
|
||||
</p>
|
||||
</div>
|
||||
{hasChanges && (
|
||||
@ -104,7 +105,7 @@ function MultiUserMode() {
|
||||
onClick={() => handleSubmit()}
|
||||
className="mt-3 mr-0 -mb-20 z-10"
|
||||
>
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</CTAButton>
|
||||
</div>
|
||||
)}
|
||||
@ -116,8 +117,8 @@ function MultiUserMode() {
|
||||
<div className="">
|
||||
<label className="mb-2.5 block font-medium text-white">
|
||||
{multiUserModeEnabled
|
||||
? "Multi-User Mode is Enabled"
|
||||
: "Enable Multi-User Mode"}
|
||||
? t("multi.enable.is-enable")
|
||||
: t("multi.enable.enable")}
|
||||
</label>
|
||||
|
||||
<label className="relative inline-flex cursor-pointer items-center">
|
||||
@ -140,7 +141,7 @@ function MultiUserMode() {
|
||||
htmlFor="username"
|
||||
className="block mb-3 font-medium text-white"
|
||||
>
|
||||
Admin account username
|
||||
{t("multi.enable.username")}
|
||||
</label>
|
||||
<input
|
||||
name="username"
|
||||
@ -159,7 +160,7 @@ function MultiUserMode() {
|
||||
htmlFor="password"
|
||||
className="block mb-3 font-medium text-white"
|
||||
>
|
||||
Admin account password
|
||||
{t("multi.enable.password")}
|
||||
</label>
|
||||
<input
|
||||
name="password"
|
||||
@ -178,9 +179,7 @@ function MultiUserMode() {
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-14">
|
||||
<p className="text-white/80 text-xs rounded-lg w-96">
|
||||
By default, you will be the only admin. As an admin you will
|
||||
need to create accounts for all new users or admins. Do not lose
|
||||
your password as only an Admin user can reset passwords.
|
||||
{t("multi.enable.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -197,6 +196,7 @@ function PasswordProtection() {
|
||||
const [multiUserModeEnabled, setMultiUserModeEnabled] = useState(false);
|
||||
const [usePassword, setUsePassword] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -269,12 +269,11 @@ function PasswordProtection() {
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Password Protection
|
||||
{t("multi.password.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
Protect your AnythingLLM instance with a password. If you forget
|
||||
this there is no recovery method so ensure you save this password.
|
||||
{t("multi.password.description")}
|
||||
</p>
|
||||
</div>
|
||||
{hasChanges && (
|
||||
@ -283,7 +282,7 @@ function PasswordProtection() {
|
||||
onClick={() => handleSubmit()}
|
||||
className="mt-3 mr-0 -mb-20 z-10"
|
||||
>
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</CTAButton>
|
||||
</div>
|
||||
)}
|
||||
@ -294,7 +293,7 @@ function PasswordProtection() {
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="">
|
||||
<label className="mb-2.5 block font-medium text-white">
|
||||
Password Protect Instance
|
||||
{t("multi.instance.title")}
|
||||
</label>
|
||||
|
||||
<label className="relative inline-flex cursor-pointer items-center">
|
||||
@ -314,7 +313,7 @@ function PasswordProtection() {
|
||||
htmlFor="password"
|
||||
className="block mb-3 font-medium text-white"
|
||||
>
|
||||
Instance password
|
||||
{t("multi.instance.password")}
|
||||
</label>
|
||||
<input
|
||||
name="password"
|
||||
@ -333,9 +332,7 @@ function PasswordProtection() {
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-14">
|
||||
<p className="text-white/80 text-xs rounded-lg w-96">
|
||||
By default, anyone with this password can log into the instance.
|
||||
Do not lose this password as only the instance maintainer is
|
||||
able to retrieve or reset the password once set.
|
||||
{t("multi.instance.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,6 +11,7 @@ import NativeTranscriptionOptions from "@/components/TranscriptionSelection/Nati
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const PROVIDERS = [
|
||||
{
|
||||
@ -39,6 +40,7 @@ export default function TranscriptionModelPreference() {
|
||||
const [selectedProvider, setSelectedProvider] = useState(null);
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -118,14 +120,11 @@ export default function TranscriptionModelPreference() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Transcription Model Preference
|
||||
{t("transcription.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
These are the credentials and settings for your preferred
|
||||
transcription model provider. Its important these keys are
|
||||
current and correct or else media files and audio will not
|
||||
transcribe.
|
||||
{t("transcription.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
@ -139,7 +138,7 @@ export default function TranscriptionModelPreference() {
|
||||
)}
|
||||
</div>
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Transcription Provider
|
||||
{t("transcription.provider")}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
|
@ -26,6 +26,7 @@ import { useModal } from "@/hooks/useModal";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
import AstraDBOptions from "@/components/VectorDBSelection/AstraDBOptions";
|
||||
import CTAButton from "@/components/lib/CTAButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function GeneralVectorDatabase() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
@ -39,6 +40,7 @@ export default function GeneralVectorDatabase() {
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
@ -194,13 +196,11 @@ export default function GeneralVectorDatabase() {
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<p className="text-lg leading-6 font-bold text-white">
|
||||
Vector Database
|
||||
{t("vector.title")}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60">
|
||||
These are the credentials and settings for how your
|
||||
AnythingLLM instance will function. It's important these keys
|
||||
are current and correct.
|
||||
{t("vector.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full justify-end flex">
|
||||
@ -209,12 +209,12 @@ export default function GeneralVectorDatabase() {
|
||||
onClick={() => handleSubmit()}
|
||||
className="mt-3 mr-0 -mb-14 z-10"
|
||||
>
|
||||
{saving ? "Saving..." : "Save changes"}
|
||||
{saving ? t("common.saving") : t("common.save")}
|
||||
</CTAButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-base font-bold text-white mt-6 mb-4">
|
||||
Vector Database Provider
|
||||
{t("vector.provider.title")}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{searchMenuOpen && (
|
||||
|
@ -4,6 +4,7 @@ import paths from "@/utils/paths";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Workspace from "@/models/workspace";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TITLE = "Create your first workspace";
|
||||
const DESCRIPTION =
|
||||
@ -17,6 +18,7 @@ export default function CreateWorkspace({
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const createWorkspaceRef = useRef();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setHeader({ title: TITLE, description: DESCRIPTION });
|
||||
@ -71,7 +73,7 @@ export default function CreateWorkspace({
|
||||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
Workspace Name
|
||||
{t("common.workspaces-name")}
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
|
@ -4,6 +4,7 @@ import AgentLLMItem from "./AgentLLMItem";
|
||||
import { AVAILABLE_LLM_PROVIDERS } from "@/pages/GeneralSettings/LLMPreference";
|
||||
import { CaretUpDown, Gauge, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import AgentModelSelection from "../AgentModelSelection";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ENABLED_PROVIDERS = [
|
||||
"openai",
|
||||
@ -65,7 +66,7 @@ export default function AgentLLMSelection({
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
function updateLLMChoice(selection) {
|
||||
setSearchQuery("");
|
||||
setSelectedLLM(selection);
|
||||
@ -96,22 +97,17 @@ export default function AgentLLMSelection({
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-x-2 text-white mb-4 bg-blue-800/30 w-fit rounded-lg px-4 py-2">
|
||||
<div className="gap-x-2 flex items-center">
|
||||
<Gauge className="shrink-0" size={25} />
|
||||
<p className="text-sm">
|
||||
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.
|
||||
</p>
|
||||
<p className="text-sm">{t("agent.performance-warning")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Agent LLM Provider
|
||||
{t("agent.provider.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific LLM provider & model that will be used for this
|
||||
workspace's @agent agent.
|
||||
{t("agent.provider.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import useGetProviderModels, {
|
||||
DISABLED_PROVIDERS,
|
||||
} from "@/hooks/useGetProvidersModels";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// These models do NOT support function calling
|
||||
function supportedModel(provider, model = "") {
|
||||
@ -19,6 +20,8 @@ export default function AgentModelSelection({
|
||||
}) {
|
||||
const { defaultModels, customModels, loading } =
|
||||
useGetProviderModels(provider);
|
||||
|
||||
const { t } = useTranslation();
|
||||
if (DISABLED_PROVIDERS.includes(provider)) return null;
|
||||
|
||||
if (loading) {
|
||||
@ -26,11 +29,10 @@ export default function AgentModelSelection({
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Agent Chat model
|
||||
{t("agent.mode.chat.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific chat model that will be used for this workspace's
|
||||
@agent agent.
|
||||
{t("agent.mode.chat.description")}
|
||||
</p>
|
||||
</div>
|
||||
<select
|
||||
@ -40,7 +42,7 @@ export default function AgentModelSelection({
|
||||
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
|
||||
>
|
||||
<option disabled={true} selected={true}>
|
||||
-- waiting for models --
|
||||
{t("agent.mode.wait")}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -51,11 +53,10 @@ export default function AgentModelSelection({
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Agent model
|
||||
{t("agent.mode.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific LLM model that will be used for this workspace's @agent
|
||||
agent.
|
||||
{t("agent.mode.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -0,0 +1,204 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import GoogleSearchIcon from "./icons/google.png";
|
||||
import SerperDotDevIcon from "./icons/serper.png";
|
||||
import BingSearchIcon from "./icons/bing.png";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import SearchProviderItem from "./SearchProviderItem";
|
||||
import {
|
||||
SerperDotDevOptions,
|
||||
GoogleSearchOptions,
|
||||
BingSearchOptions,
|
||||
} from "./SearchProviderOptions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const SEARCH_PROVIDERS = [
|
||||
{
|
||||
name: "Please make a selection",
|
||||
value: "none",
|
||||
logo: AnythingLLMIcon,
|
||||
options: () => <React.Fragment />,
|
||||
description:
|
||||
"Web search will be disabled until a provider and keys are provided.",
|
||||
},
|
||||
{
|
||||
name: "Google Search Engine",
|
||||
value: "google-search-engine",
|
||||
logo: GoogleSearchIcon,
|
||||
options: (settings) => <GoogleSearchOptions settings={settings} />,
|
||||
description:
|
||||
"Web search powered by a custom Google Search Engine. Free for 100 queries per day.",
|
||||
},
|
||||
{
|
||||
name: "Serper.dev",
|
||||
value: "serper-dot-dev",
|
||||
logo: SerperDotDevIcon,
|
||||
options: (settings) => <SerperDotDevOptions settings={settings} />,
|
||||
description:
|
||||
"Serper.dev web-search. Free account with a 2,500 calls, but then paid.",
|
||||
},
|
||||
{
|
||||
name: "Bing Search",
|
||||
value: "bing-search",
|
||||
logo: BingSearchIcon,
|
||||
options: (settings) => <BingSearchOptions settings={settings} />,
|
||||
description:
|
||||
"Web search powered by the Bing Search API. Free for 1000 queries per month.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AgentWebSearchSelection({
|
||||
skill,
|
||||
settings,
|
||||
toggleSkill,
|
||||
enabled = false,
|
||||
}) {
|
||||
const searchInputRef = useRef(null);
|
||||
const [filteredResults, setFilteredResults] = useState([]);
|
||||
const [selectedProvider, setSelectedProvider] = useState("none");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
function updateChoice(selection) {
|
||||
setSearchQuery("");
|
||||
setSelectedProvider(selection);
|
||||
setSearchMenuOpen(false);
|
||||
}
|
||||
|
||||
function handleXButton() {
|
||||
if (searchQuery.length > 0) {
|
||||
setSearchQuery("");
|
||||
if (searchInputRef.current) searchInputRef.current.value = "";
|
||||
} else {
|
||||
setSearchMenuOpen(!searchMenuOpen);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = SEARCH_PROVIDERS.filter((provider) =>
|
||||
provider.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredResults(filtered);
|
||||
}, [searchQuery, selectedProvider]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedProvider(settings?.preferences?.agent_search_provider ?? "none");
|
||||
}, [settings?.preferences?.agent_search_provider]);
|
||||
|
||||
const selectedSearchProviderObject = SEARCH_PROVIDERS.find(
|
||||
(provider) => provider.value === selectedProvider
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="border-b border-white/40 pb-4">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
{t("agent.skill.web.title")}
|
||||
</label>
|
||||
<label className="border-none relative inline-flex cursor-pointer items-center mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="peer sr-only"
|
||||
checked={enabled}
|
||||
onClick={() => toggleSkill(skill)}
|
||||
/>
|
||||
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
|
||||
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
{t("agent.skill.web.desc-start")}
|
||||
<br />
|
||||
{t("agent.skill.web.desc-end")}
|
||||
</p>
|
||||
</div>
|
||||
<div hidden={!enabled}>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="hidden"
|
||||
name="system::agent_search_provider"
|
||||
value={selectedProvider}
|
||||
/>
|
||||
{searchMenuOpen && (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
|
||||
onClick={() => setSearchMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{searchMenuOpen ? (
|
||||
<div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-[#18181B] rounded-lg flex flex-col justify-between cursor-pointer border-2 border-[#46C8FF] z-20">
|
||||
<div className="w-full flex flex-col gap-y-1">
|
||||
<div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-[#18181B]">
|
||||
<MagnifyingGlass
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="absolute left-4 z-30 text-white -ml-4 my-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
name="web-provider-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search available web-search providers"
|
||||
className="border-none -ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
<X
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="cursor-pointer text-white hover:text-[#9CA3AF]"
|
||||
onClick={handleXButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
|
||||
{filteredResults.map((provider) => {
|
||||
return (
|
||||
<SearchProviderItem
|
||||
provider={provider}
|
||||
key={provider.name}
|
||||
checked={selectedProvider === provider.value}
|
||||
onClick={() => updateChoice(provider.value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="w-full max-w-[640px] h-[64px] bg-[#18181B] rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-[#46C8FF] transition-all duration-300"
|
||||
type="button"
|
||||
onClick={() => setSearchMenuOpen(true)}
|
||||
>
|
||||
<div className="flex gap-x-4 items-center">
|
||||
<img
|
||||
src={selectedSearchProviderObject.logo}
|
||||
alt={`${selectedSearchProviderObject.name} logo`}
|
||||
className="w-10 h-10 rounded-md"
|
||||
/>
|
||||
<div className="flex flex-col text-left">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{selectedSearchProviderObject.name}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-[#D2D5DB]">
|
||||
{selectedSearchProviderObject.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CaretUpDown size={24} weight="bold" className="text-white" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{selectedProvider !== "none" && (
|
||||
<div className="mt-4 flex flex-col gap-y-1">
|
||||
{selectedSearchProviderObject.options(settings)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatHistorySettings({ workspace, setHasChanges }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-y-1 mb-4">
|
||||
<label htmlFor="name" className="block mb-2 input-label">
|
||||
Chat History
|
||||
{t("chat.history.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium">
|
||||
The number of previous chats that will be included in the
|
||||
response's short-term memory.
|
||||
<i>Recommend 20. </i>
|
||||
Anything more than 45 is likely to lead to continuous chat failures
|
||||
depending on message size.
|
||||
{t("chat.history.desc-start")}
|
||||
<i> {t("chat.history.recommend")} </i>
|
||||
{t("chat.history.desc-end")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatModeSelection({ workspace, setHasChanges }) {
|
||||
const [chatMode, setChatMode] = useState(workspace?.chatMode || "chat");
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="chatMode" className="block input-label">
|
||||
Chat mode
|
||||
{t("chat.mode.title")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -22,7 +23,7 @@ export default function ChatModeSelection({ workspace, setHasChanges }) {
|
||||
}}
|
||||
className="transition-bg duration-200 px-6 py-1 text-md text-white/60 disabled:text-white bg-transparent disabled:bg-[#687280] rounded-md"
|
||||
>
|
||||
Chat
|
||||
{t("chat.mode.chat.title")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@ -33,21 +34,23 @@ export default function ChatModeSelection({ workspace, setHasChanges }) {
|
||||
}}
|
||||
className="transition-bg duration-200 px-6 py-1 text-md text-white/60 disabled:text-white bg-transparent disabled:bg-[#687280] rounded-md"
|
||||
>
|
||||
Query
|
||||
{t("chat.mode.query.title")}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-white/60">
|
||||
{chatMode === "chat" ? (
|
||||
<>
|
||||
<b>Chat</b> will provide answers with the LLM's general knowledge{" "}
|
||||
<i className="font-semibold">and</i> document context that is
|
||||
found.
|
||||
<b>{t("chat.mode.chat.title")}</b>{" "}
|
||||
{t("chat.mode.chat.desc-start")}{" "}
|
||||
<i className="font-semibold">{t("chat.mode.chat.and")}</i>{" "}
|
||||
{t("chat.mode.chat.desc-end")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<b>Query</b> will provide answers{" "}
|
||||
<i className="font-semibold">only</i> if document context is
|
||||
found.
|
||||
<b>{t("chat.mode.query.title")}</b>{" "}
|
||||
{t("chat.mode.query.desc-start")}{" "}
|
||||
<i className="font-semibold">{t("chat.mode.query.only")}</i>{" "}
|
||||
{t("chat.mode.query.desc-end")}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useGetProviderModels, {
|
||||
DISABLED_PROVIDERS,
|
||||
} from "@/hooks/useGetProvidersModels";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatModelSelection({
|
||||
provider,
|
||||
workspace,
|
||||
@ -9,6 +9,7 @@ export default function ChatModelSelection({
|
||||
}) {
|
||||
const { defaultModels, customModels, loading } =
|
||||
useGetProviderModels(provider);
|
||||
const { t } = useTranslation();
|
||||
if (DISABLED_PROVIDERS.includes(provider)) return null;
|
||||
|
||||
if (loading) {
|
||||
@ -16,11 +17,10 @@ export default function ChatModelSelection({
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Chat model
|
||||
{t("chat.model.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific chat model that will be used for this workspace. If
|
||||
empty, will use the system LLM preference.
|
||||
{t("chat.model.description")}
|
||||
</p>
|
||||
</div>
|
||||
<select
|
||||
@ -41,11 +41,10 @@ export default function ChatModelSelection({
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Chat model
|
||||
{t("chat.model.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific chat model that will be used for this workspace. If
|
||||
empty, will use the system LLM preference.
|
||||
{t("chat.model.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { chatPrompt } from "@/utils/chat";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Prompt
|
||||
{t("chat.prompt.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The prompt that will be used on this workspace. Define the context and
|
||||
instructions for the AI to generate a response. You should to provide
|
||||
a carefully crafted prompt so the AI can generate a relevant and
|
||||
accurate response.
|
||||
{t("chat.prompt.description")}
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { chatQueryRefusalResponse } from "@/utils/chat";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function ChatQueryRefusalResponse({ workspace, setHasChanges }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Query mode refusal response
|
||||
{t("chat.refusal.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
When in <code className="bg-zinc-900 p-0.5 rounded-sm">query</code>{" "}
|
||||
mode, you may want to return a custom refusal response when no context
|
||||
is found.
|
||||
{t("chat.refusal.desc-start")}{" "}
|
||||
<code className="bg-zinc-900 p-0.5 rounded-sm">
|
||||
{t("chat.refusal.query")}
|
||||
</code>{" "}
|
||||
{t("chat.refusal.desc-end")}
|
||||
</p>
|
||||
</div>
|
||||
<textarea
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
function recommendedSettings(provider = null) {
|
||||
switch (provider) {
|
||||
case "mistral":
|
||||
@ -13,24 +14,20 @@ export default function ChatTemperatureSettings({
|
||||
setHasChanges,
|
||||
}) {
|
||||
const defaults = recommendedSettings(settings?.LLMProvider);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
LLM Temperature
|
||||
{t("chat.temperature.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
This setting controls how "creative" your LLM responses will
|
||||
be.
|
||||
{t("chat.temperature.desc-start")}
|
||||
<br />
|
||||
The higher the number the more creative. For some models this can lead
|
||||
to incoherent responses when set too high.
|
||||
{t("chat.temperature.desc-end")}
|
||||
<br />
|
||||
<br />
|
||||
<i>
|
||||
Most LLMs have various acceptable ranges of valid values. Consult
|
||||
your LLM provider for that information.
|
||||
</i>
|
||||
<i>{t("chat.temperature.hint")}</i>
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
|
@ -4,6 +4,7 @@ import WorkspaceLLMItem from "./WorkspaceLLMItem";
|
||||
import { AVAILABLE_LLM_PROVIDERS } from "@/pages/GeneralSettings/LLMPreference";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
import ChatModelSelection from "../ChatModelSelection";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Some providers can only be associated with a single model.
|
||||
// In that case there is no selection to be made so we can just move on.
|
||||
@ -34,7 +35,7 @@ export default function WorkspaceLLMSelection({
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [searchMenuOpen, setSearchMenuOpen] = useState(false);
|
||||
const searchInputRef = useRef(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
function updateLLMChoice(selection) {
|
||||
setSearchQuery("");
|
||||
setSelectedLLM(selection);
|
||||
@ -63,11 +64,10 @@ export default function WorkspaceLLMSelection({
|
||||
<div className="border-b border-white/40 pb-8">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace LLM Provider
|
||||
{t("chat.llm.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The specific LLM provider & model that will be used for this
|
||||
workspace. By default, it uses the system LLM provider and settings.
|
||||
{t("chat.llm.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -92,7 +92,7 @@ export default function WorkspaceLLMSelection({
|
||||
type="text"
|
||||
name="llm-search"
|
||||
autoComplete="off"
|
||||
placeholder="Search all LLM providers"
|
||||
placeholder={t("chat.llm.search")}
|
||||
className="-ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none focus:border-white text-white placeholder:text-white placeholder:font-medium"
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
ref={searchInputRef}
|
||||
|
@ -3,12 +3,13 @@ import { useParams } from "react-router-dom";
|
||||
import Workspace from "@/models/workspace";
|
||||
import paths from "@/utils/paths";
|
||||
import System from "@/models/system";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function DeleteWorkspace({ workspace }) {
|
||||
const { slug } = useParams();
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [canDelete, setCanDelete] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
async function fetchKeys() {
|
||||
const canDelete = await System.getCanDeleteWorkspaces();
|
||||
@ -20,7 +21,9 @@ export default function DeleteWorkspace({ workspace }) {
|
||||
const deleteWorkspace = async () => {
|
||||
if (
|
||||
!window.confirm(
|
||||
`You are about to delete your entire ${workspace.name} workspace. This will remove all vector embeddings on your vector database.\n\nThe original source files will remain untouched. This action is irreversible.`
|
||||
`${t("general.delete.confirm-start")} ${workspace.name} ${t(
|
||||
"general.delete.confirm-end"
|
||||
)}`
|
||||
)
|
||||
)
|
||||
return false;
|
||||
@ -46,7 +49,7 @@ export default function DeleteWorkspace({ workspace }) {
|
||||
type="button"
|
||||
className="w-60 mt-[40px] transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-red-500/25 text-red-200 hover:text-white hover:bg-red-600 disabled:bg-red-600 disabled:text-red-200 disabled:animate-pulse"
|
||||
>
|
||||
{deleting ? "Deleting Workspace..." : "Delete Workspace"}
|
||||
{deleting ? t("general.delete.deleting") : t("general.delete.delete")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import Workspace from "@/models/workspace";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function SuggestedChatMessages({ slug }) {
|
||||
const [suggestedMessages, setSuggestedMessages] = useState([]);
|
||||
@ -10,7 +11,7 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
const [newMessage, setNewMessage] = useState({ heading: "", message: "" });
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
async function fetchWorkspace() {
|
||||
if (!slug) return;
|
||||
@ -45,8 +46,8 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
return;
|
||||
}
|
||||
const defaultMessage = {
|
||||
heading: "Explain to me",
|
||||
message: "the benefits of AnythingLLM",
|
||||
heading: t("general.message.heading"),
|
||||
message: t("general.message.body"),
|
||||
};
|
||||
setNewMessage(defaultMessage);
|
||||
setSuggestedMessages([...suggestedMessages, { ...defaultMessage }]);
|
||||
@ -91,9 +92,11 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
if (loading)
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<label className="block input-label">Suggested Chat Messages</label>
|
||||
<label className="block input-label">
|
||||
{t("general.message.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
Customize the messages that will be suggested to your workspace users.
|
||||
{t("general.message.description")}
|
||||
</p>
|
||||
<p className="text-white text-opacity-60 text-sm font-medium mt-6">
|
||||
<PreLoader size="4" />
|
||||
@ -103,9 +106,11 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
return (
|
||||
<div className="w-screen mt-6">
|
||||
<div className="flex flex-col">
|
||||
<label className="block input-label">Suggested Chat Messages</label>
|
||||
<label className="block input-label">
|
||||
{t("general.message.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
Customize the messages that will be suggested to your workspace users.
|
||||
{t("general.message.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -169,7 +174,8 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
onClick={addMessage}
|
||||
className="flex gap-x-2 items-center justify-center mt-6 text-white text-sm hover:text-sky-400 transition-all duration-300"
|
||||
>
|
||||
Add new message <Plus className="" size={24} weight="fill" />
|
||||
{t("general.message.add")}{" "}
|
||||
<Plus className="" size={24} weight="fill" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@ -180,7 +186,7 @@ export default function SuggestedChatMessages({ slug }) {
|
||||
className="transition-all duration-300 border border-slate-200 px-4 py-2 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
|
||||
onClick={handleSaveSuggestedMessages}
|
||||
>
|
||||
Save Messages
|
||||
{t("general.message.save")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function WorkspaceName({ workspace, setHasChanges }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Workspace Name
|
||||
{t("common.workspaces-name")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
This will only change the display name of your workspace.
|
||||
{t("general.names.description")}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
|
@ -2,10 +2,11 @@ import Workspace from "@/models/workspace";
|
||||
import showToast from "@/utils/toast";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function WorkspacePfp({ workspace, slug }) {
|
||||
const [pfp, setPfp] = useState(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
async function fetchWorkspace() {
|
||||
const pfpUrl = await Workspace.fetchPfp(slug);
|
||||
@ -47,9 +48,9 @@ export default function WorkspacePfp({ workspace, slug }) {
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<div className="flex flex-col">
|
||||
<label className="block input-label">Assistant Profile Image</label>
|
||||
<label className="block input-label">{t("general.pfp.title")}</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
Customize the profile image of the assistant for this workspace.
|
||||
{t("general.pfp.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row items-center gap-8">
|
||||
@ -72,7 +73,7 @@ export default function WorkspacePfp({ workspace, slug }) {
|
||||
<div className="flex flex-col items-center justify-center p-3">
|
||||
<Plus className="w-8 h-8 text-white/80 m-2" />
|
||||
<span className="text-white text-opacity-80 text-xs font-semibold">
|
||||
Workspace Image
|
||||
{t("general.pfp.image")}
|
||||
</span>
|
||||
<span className="text-white text-opacity-60 text-xs">
|
||||
800 x 800
|
||||
@ -86,7 +87,7 @@ export default function WorkspacePfp({ workspace, slug }) {
|
||||
onClick={handleRemovePfp}
|
||||
className="mt-3 text-white text-opacity-60 text-sm font-medium hover:underline"
|
||||
>
|
||||
Remove Workspace Image
|
||||
{t("general.pfp.remove")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function DocumentSimilarityThreshold({
|
||||
workspace,
|
||||
setHasChanges,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Document similarity threshold
|
||||
{t("vector-workspace.doc.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
The minimum similarity score required for a source to be considered
|
||||
related to the chat. The higher the number, the more similar the
|
||||
source must be to the chat.
|
||||
{t("vector-workspace.doc.description")}
|
||||
</p>
|
||||
</div>
|
||||
<select
|
||||
@ -21,10 +22,10 @@ export default function DocumentSimilarityThreshold({
|
||||
onChange={() => setHasChanges(true)}
|
||||
required={true}
|
||||
>
|
||||
<option value={0.0}>No restriction</option>
|
||||
<option value={0.25}>Low (similarity score ≥ .25)</option>
|
||||
<option value={0.5}>Medium (similarity score ≥ .50)</option>
|
||||
<option value={0.75}>High (similarity score ≥ .75)</option>
|
||||
<option value={0.0}>{t("vector-workspace.doc.zero")}</option>
|
||||
<option value={0.25}>{t("vector-workspace.doc.low")}</option>
|
||||
<option value={0.5}>{t("vector-workspace.doc.medium")}</option>
|
||||
<option value={0.75}>{t("vector-workspace.doc.high")}</option>
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function MaxContextSnippets({ workspace, setHasChanges }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="name" className="block input-label">
|
||||
Max Context Snippets
|
||||
{t("vector-workspace.snippets.title")}
|
||||
</label>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
|
||||
This setting controls the maximum amount of context snippets the will
|
||||
be sent to the LLM for per chat or query.
|
||||
{t("vector-workspace.snippets.description")}
|
||||
<br />
|
||||
<i>Recommended: 4</i>
|
||||
<i>{t("vector-workspace.snippets.recommend")}</i>
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
|
@ -1,31 +1,35 @@
|
||||
import { useState } from "react";
|
||||
import Workspace from "@/models/workspace";
|
||||
import showToast from "@/utils/toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function ResetDatabase({ workspace }) {
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const resetVectorDatabase = async () => {
|
||||
if (
|
||||
!window.confirm(
|
||||
`You are about to reset this workspace's vector database. This will remove all vector embeddings currently embedded.\n\nThe original source files will remain untouched. This action is irreversible.`
|
||||
)
|
||||
)
|
||||
return false;
|
||||
if (!window.confirm(`${t("vector-workspace.reset.confirm")}`)) return false;
|
||||
|
||||
setDeleting(true);
|
||||
const success = await Workspace.wipeVectorDb(workspace.slug);
|
||||
if (!success) {
|
||||
showToast("Workspace vector database could not be reset!", "error", {
|
||||
clear: true,
|
||||
});
|
||||
showToast(
|
||||
t("vector-workspace.reset.error"),
|
||||
t("vector-workspace.common.error"),
|
||||
{
|
||||
clear: true,
|
||||
}
|
||||
);
|
||||
setDeleting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
showToast("Workspace vector database was reset!", "success", {
|
||||
clear: true,
|
||||
});
|
||||
showToast(
|
||||
t("vector-workspace.reset.success"),
|
||||
t("vector-workspace.common.success"),
|
||||
{
|
||||
clear: true,
|
||||
}
|
||||
);
|
||||
setDeleting(false);
|
||||
};
|
||||
|
||||
@ -36,7 +40,9 @@ export default function ResetDatabase({ workspace }) {
|
||||
type="button"
|
||||
className="border-none w-fit transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-red-500/25 text-red-200 hover:text-white hover:bg-red-600 disabled:bg-red-600 disabled:text-red-200 disabled:animate-pulse"
|
||||
>
|
||||
{deleting ? "Clearing vectors..." : "Reset Workspace Vector Database"}
|
||||
{deleting
|
||||
? t("vector-workspace.reset.resetting")
|
||||
: t("vector-workspace.reset.reset")}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import System from "@/models/system";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function VectorCount({ reload, workspace }) {
|
||||
const [totalVectors, setTotalVectors] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchVectorCount() {
|
||||
@ -16,9 +18,9 @@ export default function VectorCount({ reload, workspace }) {
|
||||
if (totalVectors === null)
|
||||
return (
|
||||
<div>
|
||||
<h3 className="input-label">Number of vectors</h3>
|
||||
<h3 className="input-label">{t("general.vector.title")}</h3>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium py-1">
|
||||
Total number of vectors in your vector database.
|
||||
{t("general.vector.description")}
|
||||
</p>
|
||||
<p className="text-white text-opacity-60 text-sm font-medium">
|
||||
<PreLoader size="4" />
|
||||
@ -27,7 +29,7 @@ export default function VectorCount({ reload, workspace }) {
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h3 className="input-label">Number of vectors</h3>
|
||||
<h3 className="input-label">{t("general.vector.title")}</h3>
|
||||
<p className="text-white text-opacity-60 text-sm font-medium">
|
||||
{totalVectors}
|
||||
</p>
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function VectorDBIdentifier({ workspace }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<h3 className="input-label">Vector database identifier</h3>
|
||||
<h3 className="input-label">{t("vector-workspace.identifier")}</h3>
|
||||
<p className="text-white/60 text-xs font-medium py-1"> </p>
|
||||
<p className="text-white/60 text-sm">{workspace?.slug}</p>
|
||||
</div>
|
||||
|
@ -22,6 +22,7 @@ import VectorDatabase from "./VectorDatabase";
|
||||
import Members from "./Members";
|
||||
import WorkspaceAgentConfiguration from "./AgentConfig";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const TABS = {
|
||||
"general-appearance": GeneralAppearance,
|
||||
@ -43,6 +44,7 @@ export default function WorkspaceSettings() {
|
||||
}
|
||||
|
||||
function ShowWorkspaceChat() {
|
||||
const { t } = useTranslation();
|
||||
const { slug, tab } = useParams();
|
||||
const { user } = useUser();
|
||||
const [workspace, setWorkspace] = useState(null);
|
||||
@ -85,28 +87,28 @@ function ShowWorkspaceChat() {
|
||||
<ArrowUUpLeft className="h-5 w-5" weight="fill" />
|
||||
</Link>
|
||||
<TabItem
|
||||
title="General Settings"
|
||||
title={t("workspaces—settings.general")}
|
||||
icon={<Wrench className="h-6 w-6" />}
|
||||
to={paths.workspace.settings.generalAppearance(slug)}
|
||||
/>
|
||||
<TabItem
|
||||
title="Chat Settings"
|
||||
title={t("workspaces—settings.chat")}
|
||||
icon={<ChatText className="h-6 w-6" />}
|
||||
to={paths.workspace.settings.chatSettings(slug)}
|
||||
/>
|
||||
<TabItem
|
||||
title="Vector Database"
|
||||
title={t("workspaces—settings.vector")}
|
||||
icon={<Database className="h-6 w-6" />}
|
||||
to={paths.workspace.settings.vectorDatabase(slug)}
|
||||
/>
|
||||
<TabItem
|
||||
title="Members"
|
||||
title={t("workspaces—settings.members")}
|
||||
icon={<User className="h-6 w-6" />}
|
||||
to={paths.workspace.settings.members(slug)}
|
||||
visible={["admin", "manager"].includes(user?.role)}
|
||||
/>
|
||||
<TabItem
|
||||
title="Agent Configuration"
|
||||
title={t("workspaces—settings.agent")}
|
||||
icon={<Robot className="h-6 w-6" />}
|
||||
to={paths.workspace.settings.agentConfig(slug)}
|
||||
/>
|
||||
|
@ -191,6 +191,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9":
|
||||
version "7.24.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c"
|
||||
integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
|
||||
@ -1956,6 +1963,13 @@ highlight.js@^11.9.0:
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
|
||||
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
html2canvas@^1.2.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
|
||||
@ -1974,6 +1988,20 @@ human-signals@^4.3.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
|
||||
integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==
|
||||
|
||||
i18next-browser-languagedetector@^7.2.1:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz#1968196d437b4c8db847410c7c33554f6c448f6f"
|
||||
integrity sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
|
||||
i18next@^23.11.3:
|
||||
version "23.11.3"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.11.3.tgz#d269c9c15bae9d90ab291055cfc433089ca5f77b"
|
||||
integrity sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
|
||||
ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@ -2807,6 +2835,14 @@ react-dropzone@^14.2.3:
|
||||
file-selector "^0.6.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-i18next@^14.1.1:
|
||||
version "14.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.1.1.tgz#3d942a99866555ae3552c40f9fddfa061e29d7f3"
|
||||
integrity sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.10.2, react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@ -3626,6 +3662,11 @@ vlq@^0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
||||
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
|
||||
|
||||
void-elements@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
|
@ -24,7 +24,8 @@
|
||||
"prod:server": "cd server && yarn start",
|
||||
"prod:frontend": "cd frontend && yarn build",
|
||||
"generate:cloudformation": "node cloud-deployments/aws/cloudformation/generate.mjs",
|
||||
"generate::gcp_deployment": "node cloud-deployments/gcp/deployment/generate.mjs"
|
||||
"generate::gcp_deployment": "node cloud-deployments/gcp/deployment/generate.mjs",
|
||||
"verify:translations": "cd frontend/src/locales && node verifyTranslations.mjs"
|
||||
},
|
||||
"private": false
|
||||
}
|
Loading…
Reference in New Issue
Block a user