diff --git a/collector/utils/extensions/Confluence/index.js b/collector/utils/extensions/Confluence/index.js index 6df0631075989ae39cdb29cef615fe5f2f86a08c..22df1c7fea99f6a4d39ddb7a707df5c698c221f3 100644 --- a/collector/utils/extensions/Confluence/index.js +++ b/collector/utils/extensions/Confluence/index.js @@ -3,7 +3,7 @@ const path = require("path"); const { default: slugify } = require("slugify"); const { v4 } = require("uuid"); const UrlPattern = require("url-pattern"); -const { writeToServerDocuments } = require("../../files"); +const { writeToServerDocuments, sanitizeFileName } = require("../../files"); const { tokenizeString } = require("../../tokenizer"); const { ConfluencePagesLoader, @@ -98,11 +98,11 @@ async function loadConfluence({ pageUrl, username, accessToken }, response) { console.log( `[Confluence Loader]: Saving ${doc.metadata.title} to ${outFolder}` ); - writeToServerDocuments( - data, - `${slugify(doc.metadata.title)}-${data.id}`, - outFolderPath + + const fileName = sanitizeFileName( + `${slugify(doc.metadata.title)}-${data.id}` ); + writeToServerDocuments(data, fileName, outFolderPath); }); return { diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index 9b56bb5b4d0b2e1b77a94d780e5fea389bc813b0..86b50c364c3b7d25b0e21a1283535ae614bed2cf 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -129,6 +129,11 @@ function normalizePath(filepath = "") { return result; } +function sanitizeFileName(fileName) { + if (!fileName) return fileName; + return fileName.replace(/[<>:"\/\\|?*]/g, ""); +} + module.exports = { trashFile, isTextType, @@ -137,4 +142,5 @@ module.exports = { wipeCollectorStorage, normalizePath, isWithin, + sanitizeFileName, }; diff --git a/frontend/src/components/EmbeddingSelection/LMStudioOptions/index.jsx b/frontend/src/components/EmbeddingSelection/LMStudioOptions/index.jsx index 1192ce675fed914e381c1cedda05b6b53d0a6e72..8a18fb999eb007b5a7eada0f8a334bb54363ba41 100644 --- a/frontend/src/components/EmbeddingSelection/LMStudioOptions/index.jsx +++ b/frontend/src/components/EmbeddingSelection/LMStudioOptions/index.jsx @@ -1,48 +1,112 @@ import React, { useEffect, useState } from "react"; import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { LMSTUDIO_COMMON_URLS } from "@/utils/constants"; +import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; export default function LMStudioEmbeddingOptions({ settings }) { - const [basePathValue, setBasePathValue] = useState( - settings?.EmbeddingBasePath + const { + autoDetecting: loading, + basePath, + basePathValue, + showAdvancedControls, + setShowAdvancedControls, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "lmstudio", + initialBasePath: settings?.EmbeddingBasePath, + ENDPOINTS: LMSTUDIO_COMMON_URLS, + }); + + const [maxChunkLength, setMaxChunkLength] = useState( + settings?.EmbeddingModelMaxChunkLength || 8192 ); - const [basePath, setBasePath] = useState(settings?.EmbeddingBasePath); + + const handleMaxChunkLengthChange = (e) => { + setMaxChunkLength(Number(e.target.value)); + }; return ( <div className="w-full flex flex-col gap-y-4"> - <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"> - LMStudio Base URL - </label> - <input - type="url" - name="EmbeddingBasePath" - className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="http://localhost:1234/v1" - defaultValue={settings?.EmbeddingBasePath} - onChange={(e) => setBasePathValue(e.target.value)} - onBlur={() => setBasePath(basePathValue)} - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> - <LMStudioModelSelection settings={settings} basePath={basePath} /> + <div className="w-full flex items-start gap-4"> + <LMStudioModelSelection settings={settings} basePath={basePath.value} /> <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Max embedding chunk length + <label className="text-white text-sm font-semibold block mb-2"> + Max Embedding Chunk Length </label> <input type="number" name="EmbeddingModelMaxChunkLength" - className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" placeholder="8192" min={1} + value={maxChunkLength} + onChange={handleMaxChunkLengthChange} onScroll={(e) => e.target.blur()} - defaultValue={settings?.EmbeddingModelMaxChunkLength} - required={false} + required={true} autoComplete="off" /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Maximum length of text chunks for embedding. + </p> + </div> + </div> + <div className="flex justify-start mt-4"> + <button + onClick={(e) => { + e.preventDefault(); + setShowAdvancedControls(!showAdvancedControls); + }} + className="text-white hover:text-white/70 flex items-center text-sm" + > + {showAdvancedControls ? "Hide" : "Show"} Manual Endpoint Input + {showAdvancedControls ? ( + <CaretUp size={14} className="ml-1" /> + ) : ( + <CaretDown size={14} className="ml-1" /> + )} + </button> + </div> + + <div hidden={!showAdvancedControls}> + <div className="w-full flex items-start gap-4"> + <div className="flex flex-col w-60"> + <div className="flex justify-between items-center mb-2"> + <label className="text-white text-sm font-semibold"> + LM Studio Base URL + </label> + {loading ? ( + <PreLoader size="6" /> + ) : ( + <> + {!basePathValue.value && ( + <button + onClick={handleAutoDetectClick} + className="bg-primary-button text-xs font-medium px-2 py-1 rounded-lg hover:bg-secondary hover:text-white shadow-[0_4px_14px_rgba(0,0,0,0.25)]" + > + Auto-Detect + </button> + )} + </> + )} + </div> + <input + type="url" + name="EmbeddingBasePath" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="http://localhost:1234/v1" + value={basePathValue.value} + required={true} + autoComplete="off" + spellCheck={false} + onChange={basePath.onChange} + onBlur={basePath.onBlur} + /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Enter the URL where LM Studio is running. + </p> + </div> </div> </div> </div> @@ -55,14 +119,23 @@ function LMStudioModelSelection({ settings, basePath = null }) { useEffect(() => { async function findCustomModels() { - if (!basePath || !basePath.includes("/v1")) { + if (!basePath) { setCustomModels([]); setLoading(false); return; } setLoading(true); - const { models } = await System.customModels("lmstudio", null, basePath); - setCustomModels(models || []); + try { + const { models } = await System.customModels( + "lmstudio", + null, + basePath + ); + setCustomModels(models || []); + } catch (error) { + console.error("Failed to fetch custom models:", error); + setCustomModels([]); + } setLoading(false); } findCustomModels(); @@ -71,8 +144,8 @@ function LMStudioModelSelection({ settings, basePath = null }) { if (loading || customModels.length == 0) { return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + LM Studio Embedding Model </label> <select name="EmbeddingModelPref" @@ -80,19 +153,23 @@ function LMStudioModelSelection({ settings, basePath = null }) { className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > <option disabled={true} selected={true}> - {basePath?.includes("/v1") - ? "-- loading available models --" - : "-- waiting for URL --"} + {!!basePath + ? "--loading available models--" + : "Enter LM Studio URL first"} </option> </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Select the LM Studio model for embeddings. Models will load after + entering a valid LM Studio URL. + </p> </div> ); } return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + LM Studio Embedding Model </label> <select name="EmbeddingModelPref" @@ -115,6 +192,9 @@ function LMStudioModelSelection({ settings, basePath = null }) { </optgroup> )} </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Choose the LM Studio model you want to use for generating embeddings. + </p> </div> ); } diff --git a/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx b/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx index 3213f5d39af9286749fd1e1dfe9adf0f96ab5995..fca1ae7553fbf9982a3d8be174feecaf19240ff3 100644 --- a/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx +++ b/frontend/src/components/EmbeddingSelection/OllamaOptions/index.jsx @@ -1,55 +1,122 @@ import React, { useEffect, useState } from "react"; import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { OLLAMA_COMMON_URLS } from "@/utils/constants"; +import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; export default function OllamaEmbeddingOptions({ settings }) { - const [basePathValue, setBasePathValue] = useState( - settings?.EmbeddingBasePath + const { + autoDetecting: loading, + basePath, + basePathValue, + showAdvancedControls, + setShowAdvancedControls, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "ollama", + initialBasePath: settings?.EmbeddingBasePath, + ENDPOINTS: OLLAMA_COMMON_URLS, + }); + + const [maxChunkLength, setMaxChunkLength] = useState( + settings?.EmbeddingModelMaxChunkLength || 8192 ); - const [basePath, setBasePath] = useState(settings?.EmbeddingBasePath); + + const handleMaxChunkLengthChange = (e) => { + setMaxChunkLength(Number(e.target.value)); + }; return ( <div className="w-full flex flex-col gap-y-4"> - <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"> - Ollama Base URL - </label> - <input - type="url" - name="EmbeddingBasePath" - className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="http://127.0.0.1:11434" - defaultValue={settings?.EmbeddingBasePath} - onChange={(e) => setBasePathValue(e.target.value)} - onBlur={() => setBasePath(basePathValue)} - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> - <OllamaLLMModelSelection settings={settings} basePath={basePath} /> + <div className="w-full flex items-start gap-4"> + <OllamaEmbeddingModelSelection + settings={settings} + basePath={basePath.value} + /> <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Max embedding chunk length + <label className="text-white text-sm font-semibold block mb-2"> + Max Embedding Chunk Length </label> <input type="number" name="EmbeddingModelMaxChunkLength" - className="bg-zinc-900 text-white placeholder-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" placeholder="8192" min={1} + value={maxChunkLength} + onChange={handleMaxChunkLengthChange} onScroll={(e) => e.target.blur()} - defaultValue={settings?.EmbeddingModelMaxChunkLength} - required={false} + required={true} autoComplete="off" /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Maximum length of text chunks for embedding. + </p> + </div> + </div> + <div className="flex justify-start mt-4"> + <button + onClick={(e) => { + e.preventDefault(); + setShowAdvancedControls(!showAdvancedControls); + }} + className="text-white hover:text-white/70 flex items-center text-sm" + > + {showAdvancedControls ? "Hide" : "Show"} Manual Endpoint Input + {showAdvancedControls ? ( + <CaretUp size={14} className="ml-1" /> + ) : ( + <CaretDown size={14} className="ml-1" /> + )} + </button> + </div> + + <div hidden={!showAdvancedControls}> + <div className="w-full flex items-start gap-4"> + <div className="flex flex-col w-60"> + <div className="flex justify-between items-center mb-2"> + <label className="text-white text-sm font-semibold"> + Ollama Base URL + </label> + {loading ? ( + <PreLoader size="6" /> + ) : ( + <> + {!basePathValue.value && ( + <button + onClick={handleAutoDetectClick} + className="bg-primary-button text-xs font-medium px-2 py-1 rounded-lg hover:bg-secondary hover:text-white shadow-[0_4px_14px_rgba(0,0,0,0.25)]" + > + Auto-Detect + </button> + )} + </> + )} + </div> + <input + type="url" + name="EmbeddingBasePath" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="http://127.0.0.1:11434" + value={basePathValue.value} + required={true} + autoComplete="off" + spellCheck={false} + onChange={basePath.onChange} + onBlur={basePath.onBlur} + /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Enter the URL where Ollama is running. + </p> + </div> </div> </div> </div> ); } -function OllamaLLMModelSelection({ settings, basePath = null }) { +function OllamaEmbeddingModelSelection({ settings, basePath = null }) { const [customModels, setCustomModels] = useState([]); const [loading, setLoading] = useState(true); @@ -61,8 +128,13 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { return; } setLoading(true); - const { models } = await System.customModels("ollama", null, basePath); - setCustomModels(models || []); + try { + const { models } = await System.customModels("ollama", null, basePath); + setCustomModels(models || []); + } catch (error) { + console.error("Failed to fetch custom models:", error); + setCustomModels([]); + } setLoading(false); } findCustomModels(); @@ -71,33 +143,37 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { if (loading || customModels.length == 0) { return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Embedding Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + Ollama Embedding Model </label> <select name="EmbeddingModelPref" disabled={true} - className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" + className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > <option disabled={true} selected={true}> {!!basePath - ? "-- loading available models --" - : "-- waiting for URL --"} + ? "--loading available models--" + : "Enter Ollama URL first"} </option> </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Select the Ollama model for embeddings. Models will load after + entering a valid Ollama URL. + </p> </div> ); } return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Embedding Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + Ollama Embedding Model </label> <select name="EmbeddingModelPref" required={true} - className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" + className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > {customModels.length > 0 && ( <optgroup label="Your loaded models"> @@ -115,6 +191,9 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { </optgroup> )} </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Choose the Ollama model you want to use for generating embeddings. + </p> </div> ); } diff --git a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx index 9a1c59bc73f1c5ff4880a1de1a9779d4cdc89095..d3e1df58f9e74b859202ecced94384e41ee8c2be 100644 --- a/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx +++ b/frontend/src/components/LLMSelection/LMStudioOptions/index.jsx @@ -1,13 +1,32 @@ import { useEffect, useState } from "react"; -import { Info } from "@phosphor-icons/react"; +import { Info, CaretDown, CaretUp } from "@phosphor-icons/react"; import paths from "@/utils/paths"; import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { LMSTUDIO_COMMON_URLS } from "@/utils/constants"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; export default function LMStudioOptions({ settings, showAlert = false }) { - const [basePathValue, setBasePathValue] = useState( - settings?.LMStudioBasePath + const { + autoDetecting: loading, + basePath, + basePathValue, + showAdvancedControls, + setShowAdvancedControls, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "lmstudio", + initialBasePath: settings?.LMStudioBasePath, + ENDPOINTS: LMSTUDIO_COMMON_URLS, + }); + + const [maxTokens, setMaxTokens] = useState( + settings?.LMStudioTokenLimit || 4096 ); - const [basePath, setBasePath] = useState(settings?.LMStudioBasePath); + + const handleMaxTokensChange = (e) => { + setMaxTokens(Number(e.target.value)); + }; return ( <div className="w-full flex flex-col"> @@ -28,45 +47,86 @@ export default function LMStudioOptions({ settings, showAlert = false }) { </a> </div> )} - <div className="w-full flex items-center gap-4"> + <div className="w-full flex items-start gap-4"> + <LMStudioModelSelection settings={settings} basePath={basePath.value} /> <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - LMStudio Base URL + <label className="text-white text-sm font-semibold block mb-2"> + Max Tokens </label> <input - type="url" - name="LMStudioBasePath" + type="number" + name="LMStudioTokenLimit" className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="http://localhost:1234/v1" - defaultValue={settings?.LMStudioBasePath} + placeholder="4096" + defaultChecked="4096" + min={1} + value={maxTokens} + onChange={handleMaxTokensChange} + onScroll={(e) => e.target.blur()} required={true} autoComplete="off" - spellCheck={false} - onChange={(e) => setBasePathValue(e.target.value)} - onBlur={() => setBasePath(basePathValue)} /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Maximum number of tokens for context and response. + </p> </div> - {!settings?.credentialsOnly && ( - <> - <LMStudioModelSelection settings={settings} basePath={basePath} /> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Token context window + </div> + <div className="flex justify-start mt-4"> + <button + onClick={(e) => { + e.preventDefault(); + setShowAdvancedControls(!showAdvancedControls); + }} + className="text-white hover:text-white/70 flex items-center text-sm" + > + {showAdvancedControls ? "Hide" : "Show"} Manual Endpoint Input + {showAdvancedControls ? ( + <CaretUp size={14} className="ml-1" /> + ) : ( + <CaretDown size={14} className="ml-1" /> + )} + </button> + </div> + + <div hidden={!showAdvancedControls}> + <div className="w-full flex items-start gap-4 mt-4"> + <div className="flex flex-col w-60"> + <div className="flex justify-between items-center mb-2"> + <label className="text-white text-sm font-semibold"> + LM Studio Base URL </label> - <input - type="number" - name="LMStudioTokenLimit" - className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="4096" - min={1} - onScroll={(e) => e.target.blur()} - defaultValue={settings?.LMStudioTokenLimit} - required={true} - autoComplete="off" - /> + {loading ? ( + <PreLoader size="6" /> + ) : ( + <> + {!basePathValue.value && ( + <button + onClick={handleAutoDetectClick} + className="bg-primary-button text-xs font-medium px-2 py-1 rounded-lg hover:bg-secondary hover:text-white shadow-[0_4px_14px_rgba(0,0,0,0.25)]" + > + Auto-Detect + </button> + )} + </> + )} </div> - </> - )} + <input + type="url" + name="LMStudioBasePath" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="http://localhost:1234/v1" + value={basePathValue.value} + required={true} + autoComplete="off" + spellCheck={false} + onChange={basePath.onChange} + onBlur={basePath.onBlur} + /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Enter the URL where LM Studio is running. + </p> + </div> + </div> </div> </div> ); @@ -78,14 +138,23 @@ function LMStudioModelSelection({ settings, basePath = null }) { useEffect(() => { async function findCustomModels() { - if (!basePath || !basePath.includes("/v1")) { + if (!basePath) { setCustomModels([]); setLoading(false); return; } setLoading(true); - const { models } = await System.customModels("lmstudio", null, basePath); - setCustomModels(models || []); + try { + const { models } = await System.customModels( + "lmstudio", + null, + basePath + ); + setCustomModels(models || []); + } catch (error) { + console.error("Failed to fetch custom models:", error); + setCustomModels([]); + } setLoading(false); } findCustomModels(); @@ -94,8 +163,8 @@ function LMStudioModelSelection({ settings, basePath = null }) { if (loading || customModels.length == 0) { return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + LM Studio Model </label> <select name="LMStudioModelPref" @@ -103,19 +172,23 @@ function LMStudioModelSelection({ settings, basePath = null }) { className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > <option disabled={true} selected={true}> - {basePath?.includes("/v1") - ? "-- loading available models --" - : "-- waiting for URL --"} + {!!basePath + ? "--loading available models--" + : "Enter LM Studio URL first"} </option> </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Select the LM Studio model you want to use. Models will load after + entering a valid LM Studio URL. + </p> </div> ); } return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + LM Studio Model </label> <select name="LMStudioModelPref" @@ -138,6 +211,9 @@ function LMStudioModelSelection({ settings, basePath = null }) { </optgroup> )} </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Choose the LM Studio model you want to use for your conversations. + </p> </div> ); } diff --git a/frontend/src/components/LLMSelection/OllamaLLMOptions/index.jsx b/frontend/src/components/LLMSelection/OllamaLLMOptions/index.jsx index b08f29447c4b3a1b8958c200701788b140428820..841943937adbb1abd272684f5e6cb27403f969c8 100644 --- a/frontend/src/components/LLMSelection/OllamaLLMOptions/index.jsx +++ b/frontend/src/components/LLMSelection/OllamaLLMOptions/index.jsx @@ -1,53 +1,117 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import System from "@/models/system"; +import PreLoader from "@/components/Preloader"; +import { OLLAMA_COMMON_URLS } from "@/utils/constants"; +import { CaretDown, CaretUp } from "@phosphor-icons/react"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; export default function OllamaLLMOptions({ settings }) { - const [basePathValue, setBasePathValue] = useState( - settings?.OllamaLLMBasePath + const { + autoDetecting: loading, + basePath, + basePathValue, + showAdvancedControls, + setShowAdvancedControls, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "ollama", + initialBasePath: settings?.OllamaLLMBasePath, + ENDPOINTS: OLLAMA_COMMON_URLS, + }); + + const [maxTokens, setMaxTokens] = useState( + settings?.OllamaLLMTokenLimit || 4096 ); - const [basePath, setBasePath] = useState(settings?.OllamaLLMBasePath); + + const handleMaxTokensChange = (e) => { + setMaxTokens(Number(e.target.value)); + }; return ( <div className="w-full flex flex-col gap-y-4"> - <div className="w-full flex items-center gap-4"> + <div className="w-full flex items-start gap-4"> + <OllamaLLMModelSelection + settings={settings} + basePath={basePath.value} + /> <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Ollama Base URL + <label className="text-white text-sm font-semibold block mb-2"> + Max Tokens </label> <input - type="url" - name="OllamaLLMBasePath" + type="number" + name="OllamaLLMTokenLimit" className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="http://127.0.0.1:11434" - defaultValue={settings?.OllamaLLMBasePath} + placeholder="4096" + defaultChecked="4096" + min={1} + value={maxTokens} + onChange={handleMaxTokensChange} + onScroll={(e) => e.target.blur()} required={true} autoComplete="off" - spellCheck={false} - onChange={(e) => setBasePathValue(e.target.value)} - onBlur={() => setBasePath(basePathValue)} /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Maximum number of tokens for context and response. + </p> </div> - {!settings?.credentialsOnly && ( - <> - <OllamaLLMModelSelection settings={settings} basePath={basePath} /> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Token context window + </div> + <div className="flex justify-start mt-4"> + <button + onClick={(e) => { + e.preventDefault(); + setShowAdvancedControls(!showAdvancedControls); + }} + className="text-white hover:text-white/70 flex items-center text-sm" + > + {showAdvancedControls ? "Hide" : "Show"} Manual Endpoint Input + {showAdvancedControls ? ( + <CaretUp size={14} className="ml-1" /> + ) : ( + <CaretDown size={14} className="ml-1" /> + )} + </button> + </div> + + <div hidden={!showAdvancedControls}> + <div className="w-full flex items-start gap-4"> + <div className="flex flex-col w-60"> + <div className="flex justify-between items-center mb-2"> + <label className="text-white text-sm font-semibold"> + Ollama Base URL </label> - <input - type="number" - name="OllamaLLMTokenLimit" - className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="4096" - min={1} - onScroll={(e) => e.target.blur()} - defaultValue={settings?.OllamaLLMTokenLimit} - required={true} - autoComplete="off" - /> + {loading ? ( + <PreLoader size="6" /> + ) : ( + <> + {!basePathValue.value && ( + <button + onClick={handleAutoDetectClick} + className="bg-primary-button text-xs font-medium px-2 py-1 rounded-lg hover:bg-secondary hover:text-white shadow-[0_4px_14px_rgba(0,0,0,0.25)]" + > + Auto-Detect + </button> + )} + </> + )} </div> - </> - )} + <input + type="url" + name="OllamaLLMBasePath" + className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="http://127.0.0.1:11434" + value={basePathValue.value} + required={true} + autoComplete="off" + spellCheck={false} + onChange={basePath.onChange} + onBlur={basePath.onBlur} + /> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Enter the URL where Ollama is running. + </p> + </div> + </div> </div> </div> ); @@ -65,8 +129,13 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { return; } setLoading(true); - const { models } = await System.customModels("ollama", null, basePath); - setCustomModels(models || []); + try { + const { models } = await System.customModels("ollama", null, basePath); + setCustomModels(models || []); + } catch (error) { + console.error("Failed to fetch custom models:", error); + setCustomModels([]); + } setLoading(false); } findCustomModels(); @@ -75,8 +144,8 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { if (loading || customModels.length == 0) { return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + Ollama Model </label> <select name="OllamaLLMModelPref" @@ -85,18 +154,22 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { > <option disabled={true} selected={true}> {!!basePath - ? "-- loading available models --" - : "-- waiting for URL --"} + ? "--loading available models--" + : "Enter Ollama URL first"} </option> </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Select the Ollama model you want to use. Models will load after + entering a valid Ollama URL. + </p> </div> ); } return ( <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Chat Model Selection + <label className="text-white text-sm font-semibold block mb-2"> + Ollama Model </label> <select name="OllamaLLMModelPref" @@ -119,6 +192,9 @@ function OllamaLLMModelSelection({ settings, basePath = null }) { </optgroup> )} </select> + <p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2"> + Choose the Ollama model you want to use for your conversations. + </p> </div> ); } diff --git a/frontend/src/hooks/useProviderEndpointAutoDiscovery.js b/frontend/src/hooks/useProviderEndpointAutoDiscovery.js new file mode 100644 index 0000000000000000000000000000000000000000..956b09075932e586b2c47b635d0e35006792617d --- /dev/null +++ b/frontend/src/hooks/useProviderEndpointAutoDiscovery.js @@ -0,0 +1,99 @@ +import { useEffect, useState } from "react"; +import System from "@/models/system"; +import showToast from "@/utils/toast"; + +export default function useProviderEndpointAutoDiscovery({ + provider = null, + initialBasePath = "", + ENDPOINTS = [], +}) { + const [loading, setLoading] = useState(false); + const [basePath, setBasePath] = useState(initialBasePath); + const [basePathValue, setBasePathValue] = useState(initialBasePath); + const [autoDetectAttempted, setAutoDetectAttempted] = useState(false); + const [showAdvancedControls, setShowAdvancedControls] = useState(true); + + async function autoDetect(isInitialAttempt = false) { + setLoading(true); + setAutoDetectAttempted(true); + const possibleEndpoints = []; + ENDPOINTS.forEach((endpoint) => { + possibleEndpoints.push( + new Promise((resolve, reject) => { + System.customModels(provider, null, endpoint, 2_000) + .then((results) => { + if (!results?.models || results.models.length === 0) + throw new Error("No models"); + resolve({ endpoint, models: results.models }); + }) + .catch(() => { + reject(`${provider} @ ${endpoint} did not resolve.`); + }); + }) + ); + }); + + const { endpoint, models } = await Promise.any(possibleEndpoints) + .then((resolved) => resolved) + .catch(() => { + console.error("All endpoints failed to resolve."); + return { endpoint: null, models: null }; + }); + + if (models !== null) { + setBasePath(endpoint); + setBasePathValue(endpoint); + setLoading(false); + showToast("Provider endpoint discovered automatically.", "success", { + clear: true, + }); + setShowAdvancedControls(false); + return; + } + + setLoading(false); + setShowAdvancedControls(true); + showToast( + "Couldn't automatically discover the provider endpoint. Please enter it manually.", + "info", + { clear: true } + ); + } + + function handleAutoDetectClick(e) { + e.preventDefault(); + autoDetect(); + } + + function handleBasePathChange(e) { + const value = e.target.value; + setBasePathValue(value); + } + + function handleBasePathBlur() { + setBasePath(basePathValue); + } + + useEffect(() => { + if (!initialBasePath && !autoDetectAttempted) autoDetect(true); + }, [initialBasePath, autoDetectAttempted]); + + return { + autoDetecting: loading, + autoDetectAttempted, + showAdvancedControls, + setShowAdvancedControls, + basePath: { + value: basePath, + set: setBasePathValue, + onChange: handleBasePathChange, + onBlur: handleBasePathBlur, + }, + basePathValue: { + value: basePathValue, + set: setBasePathValue, + }, + handleAutoDetectClick, + runAutoDetect: autoDetect, + }; +} diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index 5254e3bd7aca48703cf5d27bbe5e6dbcaa1017da..a851f4cfbc1e3629c13b03a7dc54d363dcd3673f 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -85,6 +85,9 @@ const TRANSLATIONS = { remove: "Remove Workspace Image", }, delete: { + title: "Delete Workspace", + description: + "Delete this workspace and all of its data. This will delete the workspace for all users.", delete: "Delete Workspace", deleting: "Deleting Workspace...", "confirm-start": "You are about to delete your entire", diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js index d0609e9a33cf0bd244f80d4eb990584a8ce69f5b..5dc2daad952c8e61e5e8f120b92a6c8277b4e184 100644 --- a/frontend/src/locales/es/common.js +++ b/frontend/src/locales/es/common.js @@ -82,6 +82,9 @@ const TRANSLATIONS = { remove: "Eliminar imagen del espacio de trabajo", }, delete: { + title: "Eliminar Espacio de Trabajo", + description: + "Eliminar este espacio de trabajo y todos sus datos. Esto eliminará el espacio de trabajo para todos los usuarios.", delete: "Eliminar espacio de trabajo", deleting: "Eliminando espacio de trabajo...", "confirm-start": "Estás a punto de eliminar tu", diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js index 9e7a82e813533d5a791e40357bdae17e5ad73c06..71c37a7ef8835ebe82b5b9c8d7884d6db2da3ca1 100644 --- a/frontend/src/locales/fr/common.js +++ b/frontend/src/locales/fr/common.js @@ -87,6 +87,9 @@ const TRANSLATIONS = { remove: "Supprimer l'image de l'espace de travail", }, delete: { + title: "Supprimer l'Espace de Travail", + description: + "Supprimer cet espace de travail et toutes ses données. Cela supprimera l'espace de travail pour tous les utilisateurs.", delete: "Supprimer l'espace de travail", deleting: "Suppression de l'espace de travail...", "confirm-start": "Vous êtes sur le point de supprimer votre", diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js index 34f9591c68d33f6f24adacd528994b1ce9e31461..da3b49f101b51f26e3749bc443da5ec59cf25ed8 100644 --- a/frontend/src/locales/ru/common.js +++ b/frontend/src/locales/ru/common.js @@ -78,6 +78,9 @@ const TRANSLATIONS = { remove: "Удалить изображение рабочего проÑтранÑтва", }, delete: { + title: "Удалить Рабочее ПроÑтранÑтво", + description: + "Удалите Ñто рабочее проÑтранÑтво и вÑе его данные. Ðто удалит рабочее проÑтранÑтво Ð´Ð»Ñ Ð²Ñех пользователей.", delete: "Удалить рабочее проÑтранÑтво", deleting: "Удаление рабочего проÑтранÑтва...", "confirm-start": "Ð’Ñ‹ ÑобираетеÑÑŒ удалить веÑÑŒ ваш", diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js index 0f30c4f74560f721616394d2086480123dd964c7..476533d12439835aaa2d55953e315ab05631315f 100644 --- a/frontend/src/locales/zh/common.js +++ b/frontend/src/locales/zh/common.js @@ -84,6 +84,8 @@ const TRANSLATIONS = { remove: "移除工作区图åƒ", }, delete: { + title: "åˆ é™¤å·¥ä½œåŒº", + description: "åˆ é™¤æ¤å·¥ä½œåŒºåŠå…¶æ‰€æœ‰æ•°æ®ã€‚è¿™å°†åˆ é™¤æ‰€æœ‰ç”¨æˆ·çš„å·¥ä½œåŒºã€‚", delete: "åˆ é™¤å·¥ä½œåŒº", deleting: "æ£åœ¨åˆ 除工作区...", "confirm-start": "您å³å°†åˆ 除整个", diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index b922457b72b799f3cef669396f4758796ea5f0bc..095244a4858d5aefd39fee9c12e9478d9cba0264 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -420,22 +420,6 @@ const System = { return { success: false, error: e.message }; }); }, - getCanDeleteWorkspaces: async function () { - return await fetch(`${API_BASE}/system/can-delete-workspaces`, { - method: "GET", - cache: "no-cache", - headers: baseHeaders(), - }) - .then((res) => { - if (!res.ok) throw new Error("Could not fetch can delete workspaces."); - return res.json(); - }) - .then((res) => res?.canDelete) - .catch((e) => { - console.error(e); - return false; - }); - }, getWelcomeMessages: async function () { return await fetch(`${API_BASE}/system/welcome-messages`, { method: "GET", @@ -512,10 +496,23 @@ const System = { return false; }); }, - customModels: async function (provider, apiKey = null, basePath = null) { + customModels: async function ( + provider, + apiKey = null, + basePath = null, + timeout = null + ) { + const controller = new AbortController(); + if (!!timeout) { + setTimeout(() => { + controller.abort("Request timed out."); + }, timeout); + } + return fetch(`${API_BASE}/system/custom-models`, { method: "POST", headers: baseHeaders(), + signal: controller.signal, body: JSON.stringify({ provider, apiKey, diff --git a/frontend/src/pages/Admin/System/index.jsx b/frontend/src/pages/Admin/System/index.jsx index bdab765a5a7c40fda535cbd04cc8924331219e3d..3924c6e8f2e4e47f0703966e868bd0fe2311ba25 100644 --- a/frontend/src/pages/Admin/System/index.jsx +++ b/frontend/src/pages/Admin/System/index.jsx @@ -8,7 +8,6 @@ import CTAButton from "@/components/lib/CTAButton"; export default function AdminSystem() { const [saving, setSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); - const [canDelete, setCanDelete] = useState(false); const [messageLimit, setMessageLimit] = useState({ enabled: false, limit: 10, @@ -18,7 +17,6 @@ export default function AdminSystem() { e.preventDefault(); setSaving(true); await Admin.updateSystemPreferences({ - users_can_delete_workspaces: canDelete, limit_user_messages: messageLimit.enabled, message_limit: messageLimit.limit, }); @@ -31,7 +29,6 @@ export default function AdminSystem() { async function fetchSettings() { const settings = (await Admin.systemPreferences())?.settings; if (!settings) return; - setCanDelete(settings?.users_can_delete_workspaces); setMessageLimit({ enabled: settings.limit_user_messages, limit: settings.message_limit, @@ -71,29 +68,6 @@ export default function AdminSystem() { </div> )} <div className="mt-4 mb-8"> - <div className="flex flex-col gap-y-1"> - <h2 className="text-base leading-6 font-bold text-white"> - Users can delete workspaces - </h2> - <p className="text-xs leading-[18px] font-base text-white/60"> - Allow non-admin users to delete workspaces that they are a part - of. This would delete the workspace for everyone. - </p> - <label className="relative inline-flex cursor-pointer items-center mt-2"> - <input - type="checkbox" - name="users_can_delete_workspaces" - checked={canDelete} - onChange={(e) => setCanDelete(e.target.checked)} - className="peer sr-only" - /> - <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> - </div> - - <div className="mb-8"> <div className="flex flex-col gap-y-1"> <h2 className="text-base leading-6 font-bold text-white"> Limit messages per user per day diff --git a/frontend/src/pages/WorkspaceSettings/GeneralAppearance/DeleteWorkspace/index.jsx b/frontend/src/pages/WorkspaceSettings/GeneralAppearance/DeleteWorkspace/index.jsx index 32a3eaa4e9e105e4d15c48dba692991980260642..44c7127ae777b219e532ef04095021016a8778e2 100644 --- a/frontend/src/pages/WorkspaceSettings/GeneralAppearance/DeleteWorkspace/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/GeneralAppearance/DeleteWorkspace/index.jsx @@ -1,22 +1,14 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; 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"; +import showToast from "@/utils/toast"; 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(); - setCanDelete(canDelete); - } - fetchKeys(); - }, [workspace?.slug]); const deleteWorkspace = async () => { if ( @@ -40,16 +32,20 @@ export default function DeleteWorkspace({ workspace }) { ? (window.location = paths.home()) : window.location.reload(); }; - - if (!canDelete) return null; return ( - <button - disabled={deleting} - onClick={deleteWorkspace} - 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 ? t("general.delete.deleting") : t("general.delete.delete")} - </button> + <div className="flex flex-col mt-10"> + <label className="block input-label">{t("general.delete.title")}</label> + <p className="text-white text-opacity-60 text-xs font-medium py-1.5"> + {t("general.delete.description")} + </p> + <button + disabled={deleting} + onClick={deleteWorkspace} + type="button" + className="w-60 mt-4 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 ? t("general.delete.deleting") : t("general.delete.delete")} + </button> + </div> ); } diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 3f637617f42e7dd29730cdf8c978b95b44d16923..a08439d0ba34c5aa3ef7354c9ba71a5b64616316 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -10,6 +10,19 @@ export const SEEN_WATCH_ALERT = "anythingllm_watched_document_alert"; export const USER_BACKGROUND_COLOR = "bg-historical-msg-user"; export const AI_BACKGROUND_COLOR = "bg-historical-msg-system"; +export const OLLAMA_COMMON_URLS = [ + "http://127.0.0.1:11434", + "http://host.docker.internal:11434", + "http://172.17.0.1:11434", +]; + +export const LMSTUDIO_COMMON_URLS = [ + "http://localhost:1234/v1", + "http://127.0.0.1:1234/v1", + "http://host.docker.internal:1234/v1", + "http://172.17.0.1:1234/v1", +]; + export function fullApiUrl() { if (API_BASE !== "/api") return API_BASE; return `${window.location.origin}/api`; diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index 67d7210f0581e9054b82e1ab3ed4c29016df2b51..d7cab1f584604037b8d2d9091fbcb7113fbf609c 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -319,9 +319,6 @@ function adminEndpoints(app) { try { const embedder = getEmbeddingEngineSelection(); const settings = { - users_can_delete_workspaces: - (await SystemSettings.get({ label: "users_can_delete_workspaces" })) - ?.value === "true", limit_user_messages: (await SystemSettings.get({ label: "limit_user_messages" })) ?.value === "true", diff --git a/server/endpoints/api/admin/index.js b/server/endpoints/api/admin/index.js index 600d36368cb17e7ffd84139f85215bca159f83ec..dd47f37469f0ac01bbc477f2e82c146967f5f7d7 100644 --- a/server/endpoints/api/admin/index.js +++ b/server/endpoints/api/admin/index.js @@ -616,7 +616,6 @@ function apiAdminEndpoints(app) { type: 'object', example: { settings: { - users_can_delete_workspaces: true, limit_user_messages: false, message_limit: 10, } @@ -641,9 +640,6 @@ function apiAdminEndpoints(app) { } const settings = { - users_can_delete_workspaces: - (await SystemSettings.get({ label: "users_can_delete_workspaces" })) - ?.value === "true", limit_user_messages: (await SystemSettings.get({ label: "limit_user_messages" })) ?.value === "true", @@ -673,7 +669,6 @@ function apiAdminEndpoints(app) { content: { "application/json": { example: { - users_can_delete_workspaces: false, limit_user_messages: true, message_limit: 5, } diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 1849a2fc4f30f9446ccefd7d7dcda07b31a17dac..05a24e5cadef0f83e6be9a82e8d81d286257653f 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -479,7 +479,6 @@ function systemEndpoints(app) { }); await SystemSettings._updateSettings({ multi_user_mode: true, - users_can_delete_workspaces: false, limit_user_messages: false, message_limit: 25, }); @@ -776,33 +775,6 @@ function systemEndpoints(app) { } ); - app.get( - "/system/can-delete-workspaces", - [validatedRequest], - async function (request, response) { - try { - if (!response.locals.multiUserMode) { - return response.status(200).json({ canDelete: true }); - } - - const user = await userFromSession(request, response); - if ([ROLES.admin, ROLES.manager].includes(user?.role)) { - return response.status(200).json({ canDelete: true }); - } - - const canDelete = await SystemSettings.canDeleteWorkspaces(); - response.status(200).json({ canDelete }); - } catch (error) { - console.error("Error fetching can delete workspaces:", error); - response.status(500).json({ - success: false, - message: "Internal server error", - canDelete: false, - }); - } - } - ); - app.get( "/system/welcome-messages", [validatedRequest, flexUserRoleValid([ROLES.all])], diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 3f44f7228c423fe541afc42eaaf35294423c664b..ea1dd01e43489ffdc08b4907a76583e5b17623a9 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -15,7 +15,6 @@ function isNullOrNaN(value) { const SystemSettings = { protectedFields: ["multi_user_mode"], supportedFields: [ - "users_can_delete_workspaces", "limit_user_messages", "message_limit", "logo_filename", @@ -302,16 +301,6 @@ const SystemSettings = { } }, - canDeleteWorkspaces: async function () { - try { - const setting = await this.get({ label: "users_can_delete_workspaces" }); - return setting?.value === "true"; - } catch (error) { - console.error(error.message); - return false; - } - }, - hasEmbeddings: async function () { try { const { Document } = require("./documents"); diff --git a/server/prisma/seed.js b/server/prisma/seed.js index 829b812ab4eb02d8fb2ca80e3c992cb55ae16b3a..c58e4556976e966b5e28e34556850e8f52e5cb2b 100644 --- a/server/prisma/seed.js +++ b/server/prisma/seed.js @@ -4,7 +4,6 @@ const prisma = new PrismaClient(); async function main() { const settings = [ { label: "multi_user_mode", value: "false" }, - { label: "users_can_delete_workspaces", value: "false" }, { label: "limit_user_messages", value: "false" }, { label: "message_limit", value: "25" }, { label: "logo_filename", value: "anything-llm.png" }, diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index 2b5ee82ef5c0ea986353e812a1c97d2eaa5c751d..571fef5922cf4f7f2b5d78a9130491143091fbfa 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -710,7 +710,6 @@ "type": "object", "example": { "settings": { - "users_can_delete_workspaces": true, "limit_user_messages": false, "message_limit": 10 } @@ -792,7 +791,6 @@ "content": { "application/json": { "example": { - "users_can_delete_workspaces": false, "limit_user_messages": true, "message_limit": 5 }