mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Merge branch 'master' of github.com:Mintplex-Labs/anything-llm
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import { Info } from "@phosphor-icons/react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
const DEFAULT_MODELS = [
|
||||
{
|
||||
id: "gemini-embedding-001",
|
||||
@@ -7,47 +10,93 @@ const DEFAULT_MODELS = [
|
||||
|
||||
export default function GeminiOptions({ settings }) {
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
API Key
|
||||
<div className="w-full flex flex-col gap-y-6">
|
||||
<div className="w-full flex flex-col gap-y-4">
|
||||
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
API Key
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="GeminiEmbeddingApiKey"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="Gemini API Key"
|
||||
defaultValue={
|
||||
settings?.GeminiEmbeddingApiKey ? "*".repeat(20) : ""
|
||||
}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Model Preference
|
||||
</label>
|
||||
<select
|
||||
name="EmbeddingModelPref"
|
||||
required={true}
|
||||
className="border-none bg-theme-settings-input-bg border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
>
|
||||
<optgroup label="Available embedding models">
|
||||
{DEFAULT_MODELS.map((model) => {
|
||||
return (
|
||||
<option
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
selected={settings?.EmbeddingModelPref === model.id}
|
||||
>
|
||||
{model.name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<div
|
||||
data-tooltip-id="embedding-output-dimensions-tooltip"
|
||||
className="flex gap-x-1 items-center mb-3"
|
||||
>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Output dimensions
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="GeminiEmbeddingApiKey"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="Gemini API Key"
|
||||
defaultValue={settings?.GeminiEmbeddingApiKey ? "*".repeat(20) : ""}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
<Info
|
||||
size={16}
|
||||
className="text-theme-text-secondary cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Model Preference
|
||||
</label>
|
||||
<select
|
||||
name="EmbeddingModelPref"
|
||||
required={true}
|
||||
className="border-none bg-theme-settings-input-bg border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
|
||||
<Tooltip
|
||||
id="embedding-output-dimensions-tooltip"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs !opacity-100"
|
||||
style={{
|
||||
maxWidth: "250px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
<optgroup label="Available embedding models">
|
||||
{DEFAULT_MODELS.map((model) => {
|
||||
return (
|
||||
<option
|
||||
key={model.id}
|
||||
value={model.id}
|
||||
selected={settings?.EmbeddingModelPref === model.id}
|
||||
>
|
||||
{model.name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
The number of dimensions the resulting output embeddings should have
|
||||
if it supports multiple dimensions output.
|
||||
<br />
|
||||
<br /> Leave blank to use the default dimensions for the selected
|
||||
model.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
name="EmbeddingOutputDimensions"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="Assume default dimensions"
|
||||
min={1}
|
||||
onScroll={(e) => e.target.blur()}
|
||||
defaultValue={settings?.EmbeddingOutputDimensions}
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,20 +30,70 @@ export default function LocalAiOptions({ settings }) {
|
||||
apiKey={apiKey}
|
||||
basePath={basePath.value}
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="flex flex-col gap-y-1 mb-2">
|
||||
<div className="flex gap-x-1 items-center">
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Local AI API Key
|
||||
</label>
|
||||
<Info
|
||||
size={16}
|
||||
data-tooltip-id="localai-api-key-tooltip"
|
||||
className="text-theme-text-secondary cursor-pointer"
|
||||
/>
|
||||
<Tooltip
|
||||
id="localai-api-key-tooltip"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs !opacity-100"
|
||||
style={{
|
||||
maxWidth: "250px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
The API key for the LocalAI server (if applicable).
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="LocalAiApiKey"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="sk-mysecretkey"
|
||||
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
onChange={(e) => setApiKeyValue(e.target.value)}
|
||||
onBlur={() => setApiKey(apiKeyValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
||||
<div className="flex flex-col w-60">
|
||||
<div
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-id="max-embedding-chunk-length-tooltip"
|
||||
className="flex gap-x-1 items-center mb-3"
|
||||
>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Max embedding chunk length
|
||||
</label>
|
||||
<Info
|
||||
size={16}
|
||||
className="text-theme-text-secondary cursor-pointer"
|
||||
/>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Max embedding chunk length
|
||||
</label>
|
||||
<Tooltip id="max-embedding-chunk-length-tooltip">
|
||||
<Tooltip
|
||||
id="max-embedding-chunk-length-tooltip"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs !opacity-100"
|
||||
style={{
|
||||
maxWidth: "250px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
Maximum length of text chunks, in characters, for embedding.
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -59,23 +109,47 @@ export default function LocalAiOptions({ settings }) {
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="flex flex-col gap-y-1 mb-2">
|
||||
<label className="text-white text-sm font-semibold flex items-center gap-x-2">
|
||||
Local AI API Key{" "}
|
||||
<p className="!text-xs !italic !font-thin">optional</p>
|
||||
<div
|
||||
data-tooltip-id="embedding-output-dimensions-tooltip"
|
||||
className="flex gap-x-1 items-center mb-3"
|
||||
>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Output dimensions
|
||||
</label>
|
||||
<Info
|
||||
size={16}
|
||||
className="text-theme-text-secondary cursor-pointer"
|
||||
/>
|
||||
<Tooltip
|
||||
id="embedding-output-dimensions-tooltip"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs !opacity-100"
|
||||
style={{
|
||||
maxWidth: "250px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
The number of dimensions the resulting output embeddings should
|
||||
have if it supports multiple dimensions output.
|
||||
<br />
|
||||
<br /> Leave blank to use the default dimensions for the selected
|
||||
model.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="LocalAiApiKey"
|
||||
type="number"
|
||||
name="EmbeddingOutputDimensions"
|
||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||
placeholder="sk-mysecretkey"
|
||||
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
|
||||
placeholder="Assume default dimensions"
|
||||
min={1}
|
||||
onScroll={(e) => e.target.blur()}
|
||||
defaultValue={settings?.EmbeddingOutputDimensions}
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
onChange={(e) => setApiKeyValue(e.target.value)}
|
||||
onBlur={() => setApiKey(apiKeyValue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function ToggleSidebarButton({ showSidebar, setShowSidebar }) {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={`hidden md:block border-none bg-transparent outline-none ring-0 transition-left duration-500 ${showSidebar ? "left-[247px]" : "absolute top-[20px] left-[30px] z-10"}`}
|
||||
className={`hidden md:block border-none bg-transparent outline-none ring-0 absolute transition-all duration-500 z-10 ${showSidebar ? "top-[18px] left-[248px]" : "top-[20px] left-[30px]"}`}
|
||||
onClick={() => setShowSidebar((prev) => !prev)}
|
||||
data-tooltip-id="sidebar-toggle"
|
||||
data-tooltip-content={
|
||||
|
||||
@@ -34,39 +34,41 @@ export default function Sidebar() {
|
||||
width: showSidebar ? "292px" : "0px",
|
||||
paddingLeft: showSidebar ? "0px" : "16px",
|
||||
}}
|
||||
className="transition-all duration-500"
|
||||
className="relative transition-all duration-500"
|
||||
>
|
||||
<div className="flex shrink-0 w-full justify-center my-[18px]">
|
||||
<div className="flex justify-between w-[250px] min-w-[250px]">
|
||||
<Link to={paths.home()} aria-label="Home">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className={`rounded max-h-[24px] object-contain transition-opacity duration-500 ${showSidebar ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
</Link>
|
||||
{canToggleSidebar && (
|
||||
<ToggleSidebarButton
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
/>
|
||||
)}
|
||||
{canToggleSidebar && (
|
||||
<ToggleSidebarButton
|
||||
showSidebar={showSidebar}
|
||||
setShowSidebar={setShowSidebar}
|
||||
/>
|
||||
)}
|
||||
<div className="overflow-hidden h-full">
|
||||
<div className="flex shrink-0 w-full justify-center my-[18px]">
|
||||
<div className="flex w-[250px] min-w-[250px]">
|
||||
<Link to={paths.home()} aria-label="Home">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className={`rounded max-h-[24px] object-contain transition-opacity duration-500 ${showSidebar ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className="relative m-[16px] rounded-[16px] bg-theme-bg-sidebar border-[2px] border-theme-sidebar-border light:border-none min-w-[250px] p-[10px] h-[calc(100%-76px)]"
|
||||
>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
<div className="flex-grow flex flex-col min-w-[235px] min-h-0">
|
||||
<div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll">
|
||||
<div className="flex flex-col gap-y-[14px]">
|
||||
<SearchBox user={user} showNewWsModal={showNewWsModal} />
|
||||
<ActiveWorkspaces />
|
||||
<div
|
||||
ref={sidebarRef}
|
||||
className="relative m-[16px] rounded-[16px] bg-theme-bg-sidebar border-[2px] border-theme-sidebar-border light:border-none min-w-[250px] p-[10px] h-[calc(100%-76px)]"
|
||||
>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
<div className="flex-grow flex flex-col min-w-[235px] min-h-0">
|
||||
<div className="relative h-[calc(100%-60px)] flex flex-col w-full justify-between pt-[10px] overflow-y-scroll no-scroll">
|
||||
<div className="flex flex-col gap-y-[14px]">
|
||||
<SearchBox user={user} showNewWsModal={showNewWsModal} />
|
||||
<ActiveWorkspaces />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 pb-3 rounded-b-[16px] bg-theme-bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-10">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 pb-3 rounded-b-[16px] bg-theme-bg-sidebar bg-opacity-80 backdrop-filter backdrop-blur-md z-10">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,6 +233,8 @@ const SystemSettings = {
|
||||
embeddingEngine === "native"
|
||||
? NativeEmbedder._getEmbeddingModel()
|
||||
: process.env.EMBEDDING_MODEL_PREF,
|
||||
EmbeddingOutputDimensions:
|
||||
process.env.EMBEDDING_OUTPUT_DIMENSIONS || null,
|
||||
EmbeddingModelMaxChunkLength:
|
||||
process.env.EMBEDDING_MODEL_MAX_CHUNK_LENGTH,
|
||||
OllamaEmbeddingBatchSize: process.env.OLLAMA_EMBEDDING_BATCH_SIZE || 1,
|
||||
|
||||
@@ -23,7 +23,10 @@ class GeminiEmbedder {
|
||||
// https://ai.google.dev/gemini-api/docs/models/gemini#text-embedding-and-embedding
|
||||
this.embeddingMaxChunkLength = MODEL_MAP[this.model] || 2_048;
|
||||
this.log(
|
||||
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}`
|
||||
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}` +
|
||||
(this.outputDimensions
|
||||
? ` - Output Dimensions: ${this.outputDimensions}`
|
||||
: " Assuming default output dimensions")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,6 +34,16 @@ class GeminiEmbedder {
|
||||
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
|
||||
}
|
||||
|
||||
get outputDimensions() {
|
||||
if (
|
||||
process.env.EMBEDDING_OUTPUT_DIMENSIONS &&
|
||||
!isNaN(process.env.EMBEDDING_OUTPUT_DIMENSIONS) &&
|
||||
process.env.EMBEDDING_OUTPUT_DIMENSIONS > 0
|
||||
)
|
||||
return parseInt(process.env.EMBEDDING_OUTPUT_DIMENSIONS);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds a single text input
|
||||
* @param {string|string[]} textInput - The text to embed
|
||||
@@ -62,6 +75,7 @@ class GeminiEmbedder {
|
||||
.create({
|
||||
model: this.model,
|
||||
input: chunk,
|
||||
dimensions: this.outputDimensions,
|
||||
})
|
||||
.then((result) => {
|
||||
resolve({ data: result?.data, error: null });
|
||||
|
||||
@@ -7,7 +7,9 @@ class LocalAiEmbedder {
|
||||
if (!process.env.EMBEDDING_MODEL_PREF)
|
||||
throw new Error("No embedding model was set.");
|
||||
|
||||
this.className = "LocalAiEmbedder";
|
||||
const { OpenAI: OpenAIApi } = require("openai");
|
||||
this.model = process.env.EMBEDDING_MODEL_PREF;
|
||||
this.openai = new OpenAIApi({
|
||||
baseURL: process.env.EMBEDDING_BASE_PATH,
|
||||
apiKey: process.env.LOCAL_AI_API_KEY ?? null,
|
||||
@@ -16,6 +18,27 @@ class LocalAiEmbedder {
|
||||
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
||||
this.maxConcurrentChunks = 50;
|
||||
this.embeddingMaxChunkLength = maximumChunkLength();
|
||||
|
||||
this.log(
|
||||
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}` +
|
||||
(this.outputDimensions
|
||||
? ` - Output Dimensions: ${this.outputDimensions}`
|
||||
: " Assuming default output dimensions")
|
||||
);
|
||||
}
|
||||
|
||||
log(text, ...args) {
|
||||
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
|
||||
}
|
||||
|
||||
get outputDimensions() {
|
||||
if (
|
||||
process.env.EMBEDDING_OUTPUT_DIMENSIONS &&
|
||||
!isNaN(process.env.EMBEDDING_OUTPUT_DIMENSIONS) &&
|
||||
process.env.EMBEDDING_OUTPUT_DIMENSIONS > 0
|
||||
)
|
||||
return parseInt(process.env.EMBEDDING_OUTPUT_DIMENSIONS);
|
||||
return null;
|
||||
}
|
||||
|
||||
async embedTextInput(textInput) {
|
||||
@@ -32,8 +55,9 @@ class LocalAiEmbedder {
|
||||
new Promise((resolve) => {
|
||||
this.openai.embeddings
|
||||
.create({
|
||||
model: process.env.EMBEDDING_MODEL_PREF,
|
||||
model: this.model,
|
||||
input: chunk,
|
||||
dimensions: this.outputDimensions,
|
||||
})
|
||||
.then((result) => {
|
||||
resolve({ data: result?.data, error: null });
|
||||
|
||||
@@ -307,6 +307,10 @@ const KEY_MAPPING = {
|
||||
envKey: "EMBEDDING_MODEL_MAX_CHUNK_LENGTH",
|
||||
checks: [nonZero],
|
||||
},
|
||||
EmbeddingOutputDimensions: {
|
||||
envKey: "EMBEDDING_OUTPUT_DIMENSIONS",
|
||||
checks: [],
|
||||
},
|
||||
OllamaEmbeddingBatchSize: {
|
||||
envKey: "OLLAMA_EMBEDDING_BATCH_SIZE",
|
||||
checks: [nonZero],
|
||||
|
||||
Reference in New Issue
Block a user