diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8a57d27bb..9ef160e72 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -50,6 +50,9 @@ const EmbedConfigSetup = lazy( () => import("@/pages/GeneralSettings/EmbedConfigs") ); const EmbedChats = lazy(() => import("@/pages/GeneralSettings/EmbedChats")); +const PrivacyAndData = lazy( + () => import("@/pages/GeneralSettings/PrivacyAndData") +); export default function App() { return ( @@ -110,6 +113,10 @@ export default function App() { path="/settings/security" element={<ManagerRoute Component={GeneralSecurity} />} /> + <Route + path="/settings/privacy" + element={<AdminRoute Component={PrivacyAndData} />} + /> <Route path="/settings/appearance" element={<ManagerRoute Component={GeneralAppearance} />} diff --git a/frontend/src/components/SettingsSidebar/index.jsx b/frontend/src/components/SettingsSidebar/index.jsx index a7aca7ffe..66f881ff6 100644 --- a/frontend/src/components/SettingsSidebar/index.jsx +++ b/frontend/src/components/SettingsSidebar/index.jsx @@ -20,6 +20,7 @@ import { CodeBlock, Barcode, ClosedCaptioning, + EyeSlash, } from "@phosphor-icons/react"; import useUser from "@/hooks/useUser"; import { USER_BACKGROUND_COLOR } from "@/utils/constants"; @@ -349,5 +350,13 @@ const SidebarOptions = ({ user = null }) => ( flex={true} allowedRole={["admin"]} /> + <Option + href={paths.settings.privacy()} + btnText="Privacy & Data" + icon={<EyeSlash className="h-5 w-5 flex-shrink-0" />} + user={user} + flex={true} + allowedRole={["admin"]} + /> </> ); diff --git a/frontend/src/pages/GeneralSettings/PrivacyAndData/index.jsx b/frontend/src/pages/GeneralSettings/PrivacyAndData/index.jsx new file mode 100644 index 000000000..dfc4b29f9 --- /dev/null +++ b/frontend/src/pages/GeneralSettings/PrivacyAndData/index.jsx @@ -0,0 +1,206 @@ +import { useEffect, useState } from "react"; +import Sidebar from "@/components/SettingsSidebar"; +import { isMobile } from "react-device-detect"; +import showToast from "@/utils/toast"; +import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { + EMBEDDING_ENGINE_PRIVACY, + LLM_SELECTION_PRIVACY, + VECTOR_DB_PRIVACY, +} from "@/pages/OnboardingFlow/Steps/DataHandling"; + +export default function PrivacyAndDataHandling() { + const [settings, setSettings] = useState({}); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function fetchSettings() { + setLoading(true); + const settings = await System.keys(); + setSettings(settings); + setLoading(false); + } + fetchSettings(); + }, []); + + return ( + <div className="w-screen h-screen overflow-hidden bg-sidebar flex"> + <Sidebar /> + <div + style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }} + className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-main-gradient w-full h-full overflow-y-scroll border-2 border-outline" + > + <div className="flex flex-col w-full px-1 md:px-20 md:py-12 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-2xl font-semibold text-white"> + Privacy & Data-Handling + </p> + </div> + <p className="text-sm font-base text-white text-opacity-60"> + This is your configuration for how connected third party providers + and AnythingLLM handle your data. + </p> + </div> + {loading ? ( + <div className="h-1/2 transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] p-[18px] h-full overflow-y-scroll"> + <div className="w-full h-full flex justify-center items-center"> + <PreLoader /> + </div> + </div> + ) : ( + <> + <ThirdParty settings={settings} /> + <TelemetryLogs settings={settings} /> + </> + )} + </div> + </div> + </div> + ); +} + +function ThirdParty({ settings }) { + const llmChoice = settings?.LLMProvider || "openai"; + const embeddingEngine = settings?.EmbeddingEngine || "openai"; + const vectorDb = settings?.VectorDB || "pinecone"; + + 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="flex items-center gap-2.5"> + <img + src={LLM_SELECTION_PRIVACY[llmChoice].logo} + alt="LLM Logo" + className="w-8 h-8 rounded" + /> + <p className="text-white text-sm font-bold"> + {LLM_SELECTION_PRIVACY[llmChoice].name} + </p> + </div> + <ul className="flex flex-col list-disc ml-4"> + {LLM_SELECTION_PRIVACY[llmChoice].description.map((desc) => ( + <li className="text-white/90 text-sm">{desc}</li> + ))} + </ul> + </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 Engine</div> + <div className="flex items-center gap-2.5"> + <img + src={EMBEDDING_ENGINE_PRIVACY[embeddingEngine].logo} + alt="LLM Logo" + className="w-8 h-8 rounded" + /> + <p className="text-white text-sm font-bold"> + {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].name} + </p> + </div> + <ul className="flex flex-col list-disc ml-4"> + {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].description.map( + (desc) => ( + <li className="text-white/90 text-sm">{desc}</li> + ) + )} + </ul> + </div> + + <div className="flex flex-col gap-y-2 pb-4"> + <div className="text-white text-base font-bold">Vector Database</div> + <div className="flex items-center gap-2.5"> + <img + src={VECTOR_DB_PRIVACY[vectorDb].logo} + alt="LLM Logo" + className="w-8 h-8 rounded" + /> + <p className="text-white text-sm font-bold"> + {VECTOR_DB_PRIVACY[vectorDb].name} + </p> + </div> + <ul className="flex flex-col list-disc ml-4"> + {VECTOR_DB_PRIVACY[vectorDb].description.map((desc) => ( + <li className="text-white/90 text-sm">{desc}</li> + ))} + </ul> + </div> + </div> + </div> + ); +} + +function TelemetryLogs({ settings }) { + const [telemetry, setTelemetry] = useState( + settings?.DisableTelemetry !== "true" + ); + async function toggleTelemetry() { + await System.updateSystem({ + DisableTelemetry: !telemetry ? "false" : "true", + }); + setTelemetry(!telemetry); + showToast( + `Anonymous Telemetry has been ${!telemetry ? "enabled" : "disabled"}.`, + "info", + { clear: true } + ); + } + + return ( + <div className="relative w-full max-h-full"> + <div className="relative rounded-lg"> + <div className="flex items-start justify-between px-6 py-4"></div> + <div className="space-y-6 flex h-full w-full"> + <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 + </label> + <label className="relative inline-flex cursor-pointer items-center"> + <input + type="checkbox" + onClick={toggleTelemetry} + checked={telemetry} + className="peer sr-only pointer-events-none" + /> + <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> + </label> + </div> + </div> + </div> + <div className="flex flex-col items-left space-y-2"> + <p className="text-white/80 text-xs rounded-lg w-96"> + All events do not record IP-address and contain{" "} + <b>no identifying</b> content, settings, chats, or other non-usage + based information. To see the list of event tags collected you can + look on{" "} + <a + href="https://github.com/search?q=repo%3AMintplex-Labs%2Fanything-llm%20.sendTelemetry(&type=code" + className="underline text-blue-400" + target="_blank" + > + Github here + </a> + . + </p> + <p className="text-white/80 text-xs rounded-lg w-96"> + As an open-source project we respect your right to privacy. We are + dedicated to building the best solution for integrating AI and + documents privately and securely. If you do decide to turn off + telemetry all we ask is to consider sending us feedback and thoughts + so that we can continue to improve AnythingLLM for you.{" "} + <a + href="mailto:team@mintplexlabs.com" + className="underline text-blue-400" + target="_blank" + > + team@mintplexlabs.com + </a> + . + </p> + </div> + </div> + </div> + ); +} diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index af3b3a9d0..bd8487842 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -29,7 +29,7 @@ import { useNavigate } from "react-router-dom"; const TITLE = "Data Handling & Privacy"; const DESCRIPTION = "We are committed to transparency and control when it comes to your personal data."; -const LLM_SELECTION_PRIVACY = { +export const LLM_SELECTION_PRIVACY = { openai: { name: "OpenAI", description: [ @@ -138,7 +138,7 @@ const LLM_SELECTION_PRIVACY = { }, }; -const VECTOR_DB_PRIVACY = { +export const VECTOR_DB_PRIVACY = { chroma: { name: "Chroma", description: [ @@ -199,7 +199,7 @@ const VECTOR_DB_PRIVACY = { }, }; -const EMBEDDING_ENGINE_PRIVACY = { +export const EMBEDDING_ENGINE_PRIVACY = { native: { name: "AnythingLLM Embedder", description: [ diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js index 6c8745af3..0f42e2237 100644 --- a/frontend/src/utils/paths.js +++ b/frontend/src/utils/paths.js @@ -113,6 +113,9 @@ export default { logs: () => { return "/settings/event-logs"; }, + privacy: () => { + return "/settings/privacy"; + }, embedSetup: () => { return `/settings/embed-config`; }, diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 680ecf4f7..dbf95238e 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -43,6 +43,7 @@ const SystemSettings = { EmbeddingModelMaxChunkLength: process.env.EMBEDDING_MODEL_MAX_CHUNK_LENGTH, LocalAiApiKey: !!process.env.LOCAL_AI_API_KEY, + DisableTelemetry: process.env.DISABLE_TELEMETRY || "false", ...(vectorDB === "pinecone" ? { PineConeKey: !!process.env.PINECONE_API_KEY, diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index d0044357e..f8e3d9cec 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -285,6 +285,10 @@ const KEY_MAPPING = { envKey: "JWT_SECRET", checks: [requiresForceMode], }, + DisableTelemetry: { + envKey: "DISABLE_TELEMETRY", + checks: [], + }, }; function isNotEmpty(input = "") {