mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Render v1.10.0 (#4892)
* Migrate to `bcryptjs` (#4767) * Replace bcrypt with bcryptjs across multiple files * dev build --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Refactor frontend legacy JSON.parse with safeJsonParse (#4759) * replace all frontend legacy JSON.parse with safeJsonParse * default collapsed sidebar menu on failed parse * remove extra check on conditional render * undo singular json parse * add guard clause and return null for `userFromStorage` * patch domainList --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Fix pagination bug in paperless-ngx data connector (#4757) * iterate over all pages in paperless-ngx data connector * add error handling and data validation * refactor to handle edge cases and null values * catch edge case to prevent infinite loop --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Fix Stale User Session with Proper `fetch` Error Handling (#4770) * add refresh user functionality * prettier * add eslint disable comment for exhaustive-deps warning in AuthContext to stop nagging about navigate func * remove unused imports and fix typo * handle unsafe parse of undefined for in-session user deleted * Refactor refreshUser function to handle errors and return structured response. Update AuthProvider to manage user data based on success status. * Remove console error logging from promise catch in System model for cleaner error handling. * change status from 404 to 400 and valid to success * Refactor error handling in AuthProvider's refreshUser logic to remove redundant catch block and streamline user session management on failure. * prettier * reorder clauses - return errors * refactor account for all user modes dev build --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Add Auth Token to Ollama Embedding Client (#4766) * Enhance OllamaEmbedder to support authentication by adding an authorization token in headers for client initialization. * Add optional Auth Token input for Ollama embedding options * move info elements --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Upgrade to Multer 2.0.0 (#4768) * upgrade to multer 2.0.0 * bump dev --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Implement Global Error Boundary (#4765) * Implement global error boundary * add 404 page for generic path catching * devbuild --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Feat/cohere agent implementation (#4703) * implement cohere agent support * run yarn lint * moderize Cohere add supported langchain method redo streaming since it was not working looping of agent calls was not functioning * change default model to real model tag add case statement for model tag * remove debug * update default * only whitelist known labels --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Upgrade MCP SDK to Latest (1.24.3) (#4773) * upgrade mcp sdk to latest (1.24.3) * Upgrade MCP version floor in package.json to 1.24.3 * fix(devcontainer): forward ports 3000/3001 (#4779) * 4601 log model on response (#4781) * add model tag to chatCompletion * add modelTag `model` to async streaming keeps default arguments for prompt token calculation where applied via explict arg * fix HF default arg * render all performance metrics as available for backward compatibility add `timestamp` to both sync/async chat methods * extract metrics string to function * Update Google Search Option Description To Reference Documentation For Rate Limits (#4789) * Update Google Search description to reference documentation for rate limits * remove --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Refactor `LLMPerformanceMonitor.measureStream()` to Use Options Object Pattern (#4786) * Refactor LLMPerformanceMonitor to use options object for measureStream parameters * Refactor invocations of `measureStream` to use options arguments * Change invocation of `measureStream` in anthropic provider to use options argument --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * hanging lint * fix unnecessary scrollbar in workspace general appearance settings tab (#4791) * fixed SuggestedChatMessages width styling * ran yarn lint --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Add Eslint Config in `/frontend` (#4785) * Add local ESLint configuration and disable rules to allow for errorless state * Remove unnecessary ESLint disable comments in AuthContext and usePromptInputStorage for cleaner code. * Update eslint-plugin-react-hooks * Configure prettier to work with eslint * Removed trailing commas from eslint config * Prettier to source code * add a v2 lint script * put back eslint-disable comments * fix eslinter and prettier application always apply --fix since we --write prettier, otherwise it fails * precaution dev build --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Refactor localWhisper to use custom FFMPEGWrapper class (#4775) * refactor localWhisper to use new custom FFMPEGWrapper class * stub tests in github actions * add back wavefile conversion to 16khz 32f to fix docker builds * use afterEach for cleanup in ffmpeg tests * remove unused FFMPEG_PATH env check * use spawnSync for ffmpeg to capture and log output * lint * revert removal of try/catch around validateAudioFile for more helpful error msgs * use readFileSync instead of createReadStream for less overhead * change import to require for fix-path and stub import in tests * refactor to singleton to preserve ffmpeg path dev build --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Refactor Managed Services in "Data Handling & Privacy" Onboarding Step to Use Their Privacy Policy URL (#4790) * Refactor non-local LLM Provider, Vector Database, and Embedding Engine privacy information to use their policy URLs instead of descriptions * Update LLM Provider, Embedding Engine, and Vector Database sections to include privacy policy links * fix broken links, lint * Update AstraDB privacy policy URL in onboarding flow * Refactor AnythingLLM Privacy & Data page to show managed provider privacy policy URLs * Update Mistral privacy policy URLs in onboarding flow for consistency * Abstract privacy policies of providers into a reusable component | Refactor Privacy & Data Handling Step of onboarding flow to focus on solely rendering that step | Move provider privacy policy maps into constants.js * Remove commented-out code for third-party provider privacy policies in Privacy and Data Handling component * Update privacy policy descriptions for consistency by adding periods at the end of sentences in ProviderPrivacy component and constants.js * rescope constants for providers * extract default to external function, add loading state --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * patch ESM import issue (#4819) * Upgrade YT Scraper (#4820) * Merge commit from fork * Update Sponsors README * fix: validate chat message input (#4811) * fix: validate chat message input * fix: align message validation for thread stream-chat endpoint --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * patch AWS credential issue in docker context (#4842) path AWS credential issue in docker context * support AWS bedrock agents with streaming (#4850) * support AWS bedrock agents with streaming * Add back error handlers from previous fix * VectorDB class migration (#4787) * Migrate Astra to class (#4722) migrate astra to class * Migrate LanceDB to class (#4721) migrate lancedb to class * Migrate Pinecone to class (#4726) migrate pinecone to class * Migrate Zilliz to class (#4729) migrate zilliz to class * Migrate Weaviate to class (#4728) migrate weaviate to class * Migrate Qdrant to class (#4727) migrate qdrant to class * Migrate Milvus to class (#4725) migrate milvus to class * Migrate Chroma to class (#4723) migrate chroma to class * Migrate Chroma Cloud to class (#4724) * migrate chroma to class * migrate chroma cloud to class * move limits to class field --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Migrate PGVector to class (#4730) * migrate pgvector to class * patch pgvector test * convert connectionString, tableName, and validateConnection to static methods * move instance properties to class fields --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Refactor Zilliz Cloud vector DB provider (#4749) simplify zilliz implementation by using milvus as base class Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * VectorDatabase base class (#4738) create generic VectorDatabase base class Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Extend VectorDatabase base class to all providers (#4755) extend VectorDatabase base class to all providers * patch lancedb import * breakout name and add generic logger * dev tag build --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Make XLSX spreadsheets visible in chat by combining sheets (#4847) * fix bug with xlsx files not being added as context * lint * fix console logs/warn/error * abstract sheet processing to function + normalize error handling * fix jsdoc * patch xlsx filename to prevent orphaned doc * reduce tokens * correct pluralization --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Remove Workspace Creation Onboarding Page (#4823) * remove create workspace step for onboarding * remove unused image * workspace creation into dedicated useEffect + use translated workspace name * dev tag --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Improved DMR support (#4863) * Improve DMR support - Autodetect models installed - Grab all models from hub.docker to show available - UI to handle render,search, install, and management of models - Support functionality for chat, stream, and agentic calls * forgot files * fix loader circle being too large fix tooltip width command adjust location of docker installer open for web platform * adjust imports * AnythingLLM Mobile live (#4864) * remove new labels on landing * minor DMR UI changes + dynamic tooltip for context management * Adjust fix path to use ESM import (#4867) * Adjust fix path to use ESM import * normalize fix-path imports and usage across the app * extract path fix logic to utils for server and collector * add helpers * repin strip-ansi in collector * fix log for localWhisper lint * Add postsettled callers to updateENV * minor refactor for context window finder * Extract Model Table to component (#4871) * Extract Model Table to component Add provider icons to header rows and installed models Light mode supported Mapping for model name id hints to provider Update DMR to filter chat models by ability since not available via hub API * linting + dev * fix incorrect import * remove race condition regression for FoundryLocal provider * remove duplicated steam method on cohere handler * feat(i18n): add Czech (cs) language translation to AnythingLLM (#4874) Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Docker model runner download from UI (#4884) * Enable downloads of DMR models from UI * add utils + dev build * linting * add fallback key to mono model provider * update announcements for 1.10.0 * bump versions to 1.10.0 --------- Co-authored-by: Marcello Fitton <106866560+angelplusultra@users.noreply.github.com> Co-authored-by: Sean Hatfield <seanhatfield5@gmail.com> Co-authored-by: Colin Perry <55003831+17ColinMiPerry@users.noreply.github.com> Co-authored-by: Irene Wang <lohas1107@gmail.com> Co-authored-by: timothycarambat <16845892+timothycarambat@users.noreply.github.com> Co-authored-by: Ocheretovich <ocheretovich@gmail.com> Co-authored-by: Vladimir Vlach <vladaman@gmail.com>
This commit is contained in:
@@ -13,6 +13,9 @@
|
||||
// "containerUser": "anythingllm",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:1-18-bookworm",
|
||||
|
||||
"forwardPorts": [3001, 3000],
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
// Docker very useful linter
|
||||
|
||||
@@ -65,9 +65,8 @@ jobs:
|
||||
tags: |
|
||||
type=raw,value=render
|
||||
type=raw,value=railway
|
||||
# Uncomment these if you want to publish specific version tags or doing a release
|
||||
# type=raw,value=render-1.9.1
|
||||
# type=raw,value=railway-1.9.1
|
||||
type=raw,value=render-1.10.0
|
||||
type=raw,value=railway-1.10.0
|
||||
|
||||
- name: Build and push multi-platform Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
|
||||
@@ -58,7 +58,7 @@ Notes:
|
||||
```yaml
|
||||
image:
|
||||
repository: mintplexlabs/anythingllm
|
||||
tag: "1.9.1"
|
||||
tag: "1.10.0"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
@@ -104,7 +104,7 @@ helm install my-anythingllm ./anythingllm -f values-secret.yaml
|
||||
| fullnameOverride | string | `""` | |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.repository | string | `"mintplexlabs/anythingllm"` | |
|
||||
| image.tag | string | `"1.9.1"` | |
|
||||
| image.tag | string | `"1.10.0"` | |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| ingress.annotations | object | `{}` | |
|
||||
| ingress.className | string | `""` | |
|
||||
|
||||
@@ -69,7 +69,7 @@ Notes:
|
||||
```yaml
|
||||
image:
|
||||
repository: mintplexlabs/anythingllm
|
||||
tag: "1.9.1"
|
||||
tag: "1.10.0"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
||||
@@ -8,7 +8,7 @@ initContainers: []
|
||||
image:
|
||||
repository: mintplexlabs/anythingllm
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "1.9.1"
|
||||
tag: "1.10.0"
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
process.env.STORAGE_DIR = "test-storage";
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Mock fix-path as a noop to prevent SIGSEGV (segfault)
|
||||
// Returns ESM-style default export for dynamic import()
|
||||
jest.mock("fix-path", () => ({ default: jest.fn() }));
|
||||
|
||||
const { FFMPEGWrapper } = require("../../../../utils/WhisperProviders/ffmpeg");
|
||||
|
||||
const describeRunner = process.env.GITHUB_ACTIONS ? describe.skip : describe;
|
||||
|
||||
describeRunner("FFMPEGWrapper", () => {
|
||||
/** @type { import("../../../../utils/WhisperProviders/ffmpeg/index").FFMPEGWrapper } */
|
||||
let ffmpeg;
|
||||
const testDir = path.resolve(__dirname, "../../../../storage/tmp");
|
||||
const inputPath = path.resolve(testDir, "test-input.wav");
|
||||
const outputPath = path.resolve(testDir, "test-output.wav");
|
||||
|
||||
beforeEach(() => {
|
||||
ffmpeg = new FFMPEGWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(inputPath)) fs.rmSync(inputPath);
|
||||
if (fs.existsSync(outputPath)) fs.rmSync(outputPath);
|
||||
});
|
||||
|
||||
it("should find ffmpeg executable", async () => {
|
||||
const knownPath = await ffmpeg.ffmpegPath();
|
||||
expect(knownPath).toBeDefined();
|
||||
expect(typeof knownPath).toBe("string");
|
||||
expect(knownPath.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should validate ffmpeg executable", async () => {
|
||||
const knownPath = await ffmpeg.ffmpegPath();
|
||||
expect(ffmpeg.isValidFFMPEG(knownPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for invalid ffmpeg path", () => {
|
||||
expect(ffmpeg.isValidFFMPEG("/invalid/path/to/ffmpeg")).toBe(false);
|
||||
});
|
||||
|
||||
it("should convert audio file to wav format", async () => {
|
||||
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });
|
||||
|
||||
const sampleUrl =
|
||||
"https://github.com/ringcentral/ringcentral-api-docs/blob/main/resources/sample1.wav?raw=true";
|
||||
|
||||
const response = await fetch(sampleUrl);
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
`Failed to download sample file: ${response.statusText}`
|
||||
);
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
fs.writeFileSync(inputPath, Buffer.from(buffer));
|
||||
|
||||
const result = await ffmpeg.convertAudioToWav(inputPath, outputPath);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(fs.existsSync(outputPath)).toBe(true);
|
||||
|
||||
const stats = fs.statSync(outputPath);
|
||||
expect(stats.size).toBeGreaterThan(0);
|
||||
}, 30000);
|
||||
|
||||
it("should throw error when conversion fails", () => {
|
||||
const nonExistentFile = path.resolve(testDir, "non-existent-file.wav");
|
||||
const outputPath = path.resolve(testDir, "test-output-fail.wav");
|
||||
|
||||
expect(async () => {
|
||||
return await ffmpeg.convertAudioToWav(nonExistentFile, outputPath);
|
||||
}).rejects.toThrow(`Input file ${nonExistentFile} does not exist.`);
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
process.env.STORAGE_DIR = "test-storage"; // needed for tests to run
|
||||
const { YoutubeTranscript } = require("../../../../../utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js");
|
||||
|
||||
describe("YoutubeTranscript", () => {
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
console.log("Skipping YoutubeTranscript test in GitHub Actions as the URLs will not resolve.");
|
||||
it('is stubbed in GitHub Actions', () => expect(true).toBe(true));
|
||||
} else {
|
||||
it("should fetch transcript from YouTube video", async () => {
|
||||
const videoId = "BJjsfNO5JTo";
|
||||
const transcript = await YoutubeTranscript.fetchTranscript(videoId, {
|
||||
lang: "en",
|
||||
});
|
||||
|
||||
expect(transcript).toBeDefined();
|
||||
expect(typeof transcript).toBe("string");
|
||||
expect(transcript.length).toBeGreaterThan(0);
|
||||
console.log("First 200 characters:", transcript.substring(0, 200) + "...");
|
||||
}, 30000);
|
||||
|
||||
it("should fetch non asr transcript from YouTube video", async () => {
|
||||
const videoId = "D111ao6wWH0";
|
||||
const transcript = await YoutubeTranscript.fetchTranscript(videoId, {
|
||||
lang: "zh-HK",
|
||||
});
|
||||
|
||||
expect(transcript).toBeDefined();
|
||||
expect(typeof transcript).toBe("string");
|
||||
expect(transcript.length).toBeGreaterThan(0);
|
||||
console.log("First 200 characters:", transcript.substring(0, 200) + "...");
|
||||
}, 30000);
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "anything-llm-document-collector",
|
||||
"version": "1.9.1",
|
||||
"version": "1.10.0",
|
||||
"description": "Document collector server endpoints",
|
||||
"main": "index.js",
|
||||
"author": "Timothy Carambat (Mintplex Labs)",
|
||||
@@ -22,7 +22,7 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"epub2": "git+https://github.com/Mintplex-Labs/epub2-static.git#main",
|
||||
"express": "^4.21.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fix-path": "^4.0.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"ignore": "^5.3.0",
|
||||
"js-tiktoken": "^1.0.8",
|
||||
@@ -39,16 +39,22 @@
|
||||
"puppeteer": "~21.5.2",
|
||||
"sharp": "^0.33.5",
|
||||
"slugify": "^1.6.6",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"tesseract.js": "^6.0.0",
|
||||
"url-pattern": "^1.0.3",
|
||||
"uuid": "^9.0.0",
|
||||
"wavefile": "^11.0.0",
|
||||
"winston": "^3.13.0",
|
||||
"youtube-transcript-plus": "^1.1.2",
|
||||
"youtubei.js": "^9.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^2.4.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"string-width": "^4.2.3",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ const {
|
||||
trashFile,
|
||||
writeToServerDocuments,
|
||||
documentsFolder,
|
||||
directUploadsFolder,
|
||||
} = require("../../utils/files");
|
||||
const { tokenizeString } = require("../../utils/tokenizer");
|
||||
const { default: slugify } = require("slugify");
|
||||
@@ -34,30 +33,83 @@ async function asXlsx({
|
||||
metadata = {},
|
||||
}) {
|
||||
const documents = [];
|
||||
const folderName = slugify(`${path.basename(filename)}-${v4().slice(0, 4)}`, {
|
||||
lower: true,
|
||||
trim: true,
|
||||
});
|
||||
const outFolderPath = options.parseOnly
|
||||
? path.resolve(directUploadsFolder, folderName)
|
||||
: path.resolve(documentsFolder, folderName);
|
||||
|
||||
try {
|
||||
const workSheetsFromFile = xlsx.parse(fullFilePath);
|
||||
if (!fs.existsSync(outFolderPath))
|
||||
fs.mkdirSync(outFolderPath, { recursive: true });
|
||||
|
||||
for (const sheet of workSheetsFromFile) {
|
||||
try {
|
||||
const { name, data } = sheet;
|
||||
const content = convertToCSV(data);
|
||||
if (options.parseOnly) {
|
||||
const allSheetContents = [];
|
||||
let totalWordCount = 0;
|
||||
const sheetNames = [];
|
||||
|
||||
if (!content?.length) {
|
||||
console.warn(`Sheet "${name}" is empty. Skipping.`);
|
||||
continue;
|
||||
for (const sheet of workSheetsFromFile) {
|
||||
const processed = processSheet(sheet);
|
||||
if (!processed) continue;
|
||||
|
||||
const { name, content, wordCount } = processed;
|
||||
sheetNames.push(name);
|
||||
allSheetContents.push(`\nSheet: ${name}\n${content}`);
|
||||
totalWordCount += wordCount;
|
||||
}
|
||||
|
||||
if (allSheetContents.length === 0) {
|
||||
console.log(`No valid sheets found in ${filename}.`);
|
||||
return {
|
||||
success: false,
|
||||
reason: `No valid sheets found in ${filename}.`,
|
||||
documents: [],
|
||||
};
|
||||
}
|
||||
|
||||
const combinedContent = allSheetContents.join("\n");
|
||||
const sheetListText =
|
||||
sheetNames.length > 1
|
||||
? ` (Sheets: ${sheetNames.join(", ")})`
|
||||
: ` (Sheet: ${sheetNames[0]})`;
|
||||
|
||||
const combinedData = {
|
||||
id: v4(),
|
||||
url: `file://${fullFilePath}`,
|
||||
title: metadata.title || `${filename}${sheetListText}`,
|
||||
docAuthor: metadata.docAuthor || "Unknown",
|
||||
description:
|
||||
metadata.description ||
|
||||
`Spreadsheet data from ${filename} containing ${sheetNames.length} ${
|
||||
sheetNames.length === 1 ? "sheet" : "sheets"
|
||||
}`,
|
||||
docSource: metadata.docSource || "an xlsx file uploaded by the user.",
|
||||
chunkSource: metadata.chunkSource || "",
|
||||
published: createdDate(fullFilePath),
|
||||
wordCount: totalWordCount,
|
||||
pageContent: combinedContent,
|
||||
token_count_estimate: tokenizeString(combinedContent),
|
||||
};
|
||||
|
||||
const document = writeToServerDocuments({
|
||||
data: combinedData,
|
||||
filename: `${slugify(path.basename(filename))}-${combinedData.id}`,
|
||||
destinationOverride: null,
|
||||
options: { parseOnly: true },
|
||||
});
|
||||
documents.push(document);
|
||||
console.log(`[SUCCESS]: ${filename} converted & ready for embedding.`);
|
||||
} else {
|
||||
const folderName = slugify(
|
||||
`${path.basename(filename)}-${v4().slice(0, 4)}`,
|
||||
{
|
||||
lower: true,
|
||||
trim: true,
|
||||
}
|
||||
);
|
||||
const outFolderPath = path.resolve(documentsFolder, folderName);
|
||||
if (!fs.existsSync(outFolderPath))
|
||||
fs.mkdirSync(outFolderPath, { recursive: true });
|
||||
|
||||
console.log(`-- Processing sheet: ${name} --`);
|
||||
for (const sheet of workSheetsFromFile) {
|
||||
const processed = processSheet(sheet);
|
||||
if (!processed) continue;
|
||||
|
||||
const { name, content, wordCount } = processed;
|
||||
const sheetData = {
|
||||
id: v4(),
|
||||
url: `file://${path.join(outFolderPath, `${slugify(name)}.csv`)}`,
|
||||
@@ -68,7 +120,7 @@ async function asXlsx({
|
||||
docSource: metadata.docSource || "an xlsx file uploaded by the user.",
|
||||
chunkSource: metadata.chunkSource || "",
|
||||
published: createdDate(fullFilePath),
|
||||
wordCount: content.split(/\s+/).length,
|
||||
wordCount: wordCount,
|
||||
pageContent: content,
|
||||
token_count_estimate: tokenizeString(content),
|
||||
};
|
||||
@@ -83,9 +135,6 @@ async function asXlsx({
|
||||
console.log(
|
||||
`[SUCCESS]: Sheet "${name}" converted & ready for embedding.`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`Error processing sheet "${name}":`, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -114,4 +163,31 @@ async function asXlsx({
|
||||
return { success: true, reason: null, documents };
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a single sheet and returns its content and metadata
|
||||
* @param {{name: string, data: Array<Array<string|number|null|undefined>>}} sheet - Parsed sheet with name and 2D array of cell values
|
||||
* @returns {{name: string, content: string, wordCount: number}|null} - Object with name, CSV content, and word count, or null if sheet is empty
|
||||
*/
|
||||
function processSheet(sheet) {
|
||||
try {
|
||||
const { name, data } = sheet;
|
||||
const content = convertToCSV(data);
|
||||
|
||||
if (!content?.length) {
|
||||
console.log(`Sheet "${name}" is empty. Skipping.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`-- Processing sheet: ${name} --`);
|
||||
return {
|
||||
name,
|
||||
content,
|
||||
wordCount: content.split(/\s+/).length,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`Error processing sheet "${sheet.name}":`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = asXlsx;
|
||||
|
||||
114
collector/utils/WhisperProviders/ffmpeg/index.js
Normal file
114
collector/utils/WhisperProviders/ffmpeg/index.js
Normal file
@@ -0,0 +1,114 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync, spawnSync } = require("child_process");
|
||||
const { patchShellEnvironmentPath } = require("../../shell");
|
||||
/**
|
||||
* Custom FFMPEG wrapper class for audio file conversion.
|
||||
* Replaces deprecated fluent-ffmpeg package.
|
||||
* Locates ffmpeg binary and converts audio files to required
|
||||
* WAV format (16k hz mono 32f) for Whisper transcription.
|
||||
*
|
||||
* @class FFMPEGWrapper
|
||||
*/
|
||||
class FFMPEGWrapper {
|
||||
static _instance;
|
||||
|
||||
constructor() {
|
||||
if (FFMPEGWrapper._instance) return FFMPEGWrapper._instance;
|
||||
FFMPEGWrapper._instance = this;
|
||||
this._ffmpegPath = null;
|
||||
}
|
||||
|
||||
log(text, ...args) {
|
||||
console.log(`\x1b[35m[FFMPEG]\x1b[0m ${text}`, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates ffmpeg binary.
|
||||
* Uses fix-path on non-Windows platforms to ensure we can find ffmpeg.
|
||||
*
|
||||
* @returns {Promise<string>} Path to ffmpeg binary
|
||||
* @throws {Error}
|
||||
*/
|
||||
async ffmpegPath() {
|
||||
if (this._ffmpegPath) return this._ffmpegPath;
|
||||
await patchShellEnvironmentPath();
|
||||
|
||||
try {
|
||||
const which = process.platform === "win32" ? "where" : "which";
|
||||
const result = execSync(`${which} ffmpeg`, { encoding: "utf8" }).trim();
|
||||
const candidatePath = result?.split("\n")?.[0]?.trim();
|
||||
if (!candidatePath) throw new Error("FFMPEG candidate path not found.");
|
||||
if (!this.isValidFFMPEG(candidatePath))
|
||||
throw new Error("FFMPEG candidate path is not valid ffmpeg binary.");
|
||||
|
||||
this.log(`Found FFMPEG binary at ${candidatePath}`);
|
||||
this._ffmpegPath = candidatePath;
|
||||
return this._ffmpegPath;
|
||||
} catch (error) {
|
||||
this.log(error.message);
|
||||
}
|
||||
|
||||
throw new Error("FFMPEG binary not found.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that path points to a valid ffmpeg binary.
|
||||
* Runs ffmpeg -version command.
|
||||
*
|
||||
* @param {string} pathToTest - Path of ffmpeg binary
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidFFMPEG(pathToTest) {
|
||||
try {
|
||||
if (!pathToTest || !fs.existsSync(pathToTest)) return false;
|
||||
execSync(`"${pathToTest}" -version`, { encoding: "utf8", stdio: "pipe" });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts audio file to WAV format with required parameters for Whisper.
|
||||
* Output: 16k hz, mono, 32bit float.
|
||||
*
|
||||
* @param {string} inputPath - Input path for audio file (any format supported by ffmpeg)
|
||||
* @param {string} outputPath - Output path for converted file
|
||||
* @returns {Promise<boolean>}
|
||||
* @throws {Error} If ffmpeg binary cannot be found or conversion fails
|
||||
*/
|
||||
async convertAudioToWav(inputPath, outputPath) {
|
||||
if (!fs.existsSync(inputPath))
|
||||
throw new Error(`Input file ${inputPath} does not exist.`);
|
||||
const outputDir = path.dirname(outputPath);
|
||||
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
this.log(`Converting ${path.basename(inputPath)} to WAV format...`);
|
||||
// Convert to 16k hz mono 32f
|
||||
const result = spawnSync(
|
||||
await this.ffmpegPath(),
|
||||
[
|
||||
"-i",
|
||||
inputPath,
|
||||
"-ar",
|
||||
"16000",
|
||||
"-ac",
|
||||
"1",
|
||||
"-acodec",
|
||||
"pcm_f32le",
|
||||
"-y",
|
||||
outputPath,
|
||||
],
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
|
||||
// ffmpeg writes progress to stderr
|
||||
if (result.stderr) this.log(result.stderr.trim());
|
||||
if (result.status !== 0) throw new Error(`FFMPEG conversion failed`);
|
||||
this.log(`Conversion complete: ${path.basename(outputPath)}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { FFMPEGWrapper };
|
||||
@@ -64,52 +64,21 @@ class LocalWhisper {
|
||||
try {
|
||||
let buffer;
|
||||
const wavefile = require("wavefile");
|
||||
const ffmpeg = require("fluent-ffmpeg");
|
||||
const { FFMPEGWrapper } = require("./ffmpeg");
|
||||
const ffmpeg = new FFMPEGWrapper();
|
||||
const outFolder = path.resolve(__dirname, `../../storage/tmp`);
|
||||
if (!fs.existsSync(outFolder))
|
||||
fs.mkdirSync(outFolder, { recursive: true });
|
||||
|
||||
const fileExtension = path.extname(sourcePath).toLowerCase();
|
||||
if (fileExtension !== ".wav") {
|
||||
this.#log(
|
||||
`File conversion required! ${fileExtension} file detected - converting to .wav`
|
||||
const outputFile = path.resolve(outFolder, `${v4()}.wav`);
|
||||
const success = await ffmpeg.convertAudioToWav(sourcePath, outputFile);
|
||||
if (!success)
|
||||
throw new Error(
|
||||
"[Conversion Failed]: Could not convert file to .wav format!"
|
||||
);
|
||||
const outputFile = path.resolve(outFolder, `${v4()}.wav`);
|
||||
const convert = new Promise((resolve) => {
|
||||
ffmpeg(sourcePath)
|
||||
.toFormat("wav")
|
||||
.on("error", (error) => {
|
||||
this.#log(`Conversion Error! ${error.message}`);
|
||||
resolve(false);
|
||||
})
|
||||
.on("progress", (progress) =>
|
||||
this.#log(
|
||||
`Conversion Processing! ${progress.targetSize}KB converted`
|
||||
)
|
||||
)
|
||||
.on("end", () => {
|
||||
this.#log(`Conversion Complete! File converted to .wav!`);
|
||||
resolve(true);
|
||||
})
|
||||
.save(outputFile);
|
||||
});
|
||||
const success = await convert;
|
||||
if (!success)
|
||||
throw new Error(
|
||||
"[Conversion Failed]: Could not convert file to .wav format!"
|
||||
);
|
||||
|
||||
const chunks = [];
|
||||
const stream = fs.createReadStream(outputFile);
|
||||
for await (let chunk of stream) chunks.push(chunk);
|
||||
buffer = Buffer.concat(chunks);
|
||||
fs.rmSync(outputFile);
|
||||
} else {
|
||||
const chunks = [];
|
||||
const stream = fs.createReadStream(sourcePath);
|
||||
for await (let chunk of stream) chunks.push(chunk);
|
||||
buffer = Buffer.concat(chunks);
|
||||
}
|
||||
buffer = fs.readFileSync(outputFile);
|
||||
fs.rmSync(outputFile);
|
||||
|
||||
const wavFile = new wavefile.WaveFile(buffer);
|
||||
try {
|
||||
@@ -119,6 +88,9 @@ class LocalWhisper {
|
||||
throw new Error(`Invalid audio file: ${error.message}`);
|
||||
}
|
||||
|
||||
// Although we use ffmpeg to convert to the correct format (16k hz 32f),
|
||||
// different versions of ffmpeg produce different results based on the
|
||||
// environment. To ensure consistency, we convert to the correct format again.
|
||||
wavFile.toBitDepth("32f");
|
||||
wavFile.toSampleRate(16000);
|
||||
|
||||
@@ -164,7 +136,7 @@ class LocalWhisper {
|
||||
progress_callback: (data) => {
|
||||
if (!data.hasOwnProperty("progress")) return;
|
||||
console.log(
|
||||
`\x1b[34m[Embedding - Downloading Model Files]\x1b[0m ${
|
||||
`\x1b[34m[ONNXWhisper - Downloading Model Files]\x1b[0m ${
|
||||
data.file
|
||||
} ${~~data?.progress}%`
|
||||
);
|
||||
|
||||
@@ -26,19 +26,48 @@ class PaperlessNgxLoader {
|
||||
*/
|
||||
async fetchAllDocuments() {
|
||||
try {
|
||||
const documents = await fetch(`${this.baseUrl}/api/documents/`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...this.baseHeaders,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.results || [])
|
||||
.catch((error) => {
|
||||
throw new Error(
|
||||
`Failed to fetch documents from Paperless-ngx: ${error.message}`
|
||||
const documents = [];
|
||||
let nextUrl = `${this.baseUrl}/api/documents/`;
|
||||
let page = 1;
|
||||
|
||||
while (nextUrl) {
|
||||
console.log(`Fetching documents page ${page} from Paperless-ngx`);
|
||||
try {
|
||||
const data = await fetch(nextUrl, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...this.baseHeaders,
|
||||
},
|
||||
}).then((res) => {
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
`Failed to fetch documents from Paperless-ngx: ${res.status}`
|
||||
);
|
||||
return res.json();
|
||||
});
|
||||
|
||||
const validResults = data.results.filter((doc) => doc?.id);
|
||||
if (!validResults.length) break;
|
||||
|
||||
documents.push(...validResults);
|
||||
|
||||
if (data.next === nextUrl) break;
|
||||
nextUrl = data.next || null;
|
||||
page++;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching page ${page} from Paperless-ngx:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Fetched ${documents.length} documents from Paperless-ngx (Pages: ${
|
||||
page - 1
|
||||
})`
|
||||
);
|
||||
|
||||
const documentsWithContent = await Promise.all(
|
||||
documents.map(async (doc) => {
|
||||
|
||||
@@ -54,13 +54,15 @@ class YoutubeLoader {
|
||||
source: this.#videoId,
|
||||
};
|
||||
try {
|
||||
const { YoutubeTranscript } = require("./youtube-transcript");
|
||||
transcript = await YoutubeTranscript.fetchTranscript(this.#videoId, {
|
||||
const fetchTranscript = await import("youtube-transcript-plus").then(
|
||||
(module) => module.fetchTranscript
|
||||
);
|
||||
const transcriptSegments = await fetchTranscript(this.#videoId, {
|
||||
lang: this.#language,
|
||||
});
|
||||
if (!transcript) {
|
||||
if (!transcriptSegments || transcriptSegments.length === 0)
|
||||
throw new Error("Transcription not found");
|
||||
}
|
||||
transcript = this.#convertTranscriptSegmentsToText(transcriptSegments);
|
||||
if (this.#addVideoInfo) {
|
||||
const { Innertube } = require("youtubei.js");
|
||||
const youtube = await Innertube.create();
|
||||
@@ -82,6 +84,16 @@ class YoutubeLoader {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
#convertTranscriptSegmentsToText(transcriptSegments) {
|
||||
return transcriptSegments
|
||||
.map((segment) =>
|
||||
typeof segment === "string" ? segment : segment.text || ""
|
||||
)
|
||||
.join(" ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.YoutubeLoader = YoutubeLoader;
|
||||
|
||||
25
collector/utils/shell.js
Normal file
25
collector/utils/shell.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Patch the shell environment path to ensure the PATH is properly set for the current platform.
|
||||
* On Docker, we are on Node v18 and cannot support fix-path v5.
|
||||
* So we need to use the ESM-style import() to import the fix-path module + add the strip-ansi call to patch the PATH, which is the only change between v4 and v5.
|
||||
* https://github.com/sindresorhus/fix-path/issues/6
|
||||
* @returns {Promise<{[key: string]: string}>} - Environment variables from shell
|
||||
*/
|
||||
async function patchShellEnvironmentPath() {
|
||||
try {
|
||||
if (process.platform === "win32") return process.env;
|
||||
const { default: fixPath } = await import("fix-path");
|
||||
const { default: stripAnsi } = await import("strip-ansi");
|
||||
fixPath();
|
||||
if (process.env.PATH) process.env.PATH = stripAnsi(process.env.PATH);
|
||||
console.log("Shell environment path patched successfully.");
|
||||
return process.env;
|
||||
} catch (error) {
|
||||
console.error("Failed to patch shell environment path:", error);
|
||||
return process.env;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
patchShellEnvironmentPath,
|
||||
};
|
||||
@@ -521,11 +521,6 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.1.0:
|
||||
version "6.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041"
|
||||
integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||
@@ -566,11 +561,6 @@ ast-types@^0.13.4:
|
||||
dependencies:
|
||||
tslib "^2.0.1"
|
||||
|
||||
async@^0.2.9:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
|
||||
integrity sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
|
||||
@@ -1007,7 +997,7 @@ cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-spawn@^7.0.1, cross-spawn@^7.0.6:
|
||||
cross-spawn@^7.0.1, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||
@@ -1152,6 +1142,11 @@ deepmerge@^4.3.1:
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
|
||||
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
|
||||
|
||||
default-shell@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/default-shell/-/default-shell-2.2.0.tgz#31481c19747bfe59319b486591643eaf115a1864"
|
||||
integrity sha512-sPpMZcVhRQ0nEMDtuMJ+RtCxt7iHPAMBU+I4tAlo5dU1sjRpNax0crj6nR3qKpvVnckaQ9U38enXcwW9nZJeCw==
|
||||
|
||||
define-data-property@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
||||
@@ -1259,11 +1254,6 @@ dunder-proto@^1.0.1:
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
@@ -1274,11 +1264,6 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
enabled@2.0.x:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
|
||||
@@ -1419,6 +1404,21 @@ events@^3.3.0:
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||
|
||||
execa@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
|
||||
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.3"
|
||||
get-stream "^6.0.0"
|
||||
human-signals "^2.1.0"
|
||||
is-stream "^2.0.0"
|
||||
merge-stream "^2.0.0"
|
||||
npm-run-path "^4.0.1"
|
||||
onetime "^5.1.2"
|
||||
signal-exit "^3.0.3"
|
||||
strip-final-newline "^2.0.0"
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
@@ -1538,6 +1538,13 @@ finalhandler@~1.3.1:
|
||||
statuses "~2.0.2"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
fix-path@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fix-path/-/fix-path-4.0.0.tgz#bc1d14f038edb734ac46944a45454106952ca429"
|
||||
integrity sha512-g31GX207Tt+psI53ZSaB1egprYbEN0ZYl90aKcO22A2LmCNnFsSq3b5YpoKp3E/QEiWByTXGJOkFQG4S07Bc1A==
|
||||
dependencies:
|
||||
shell-path "^3.0.0"
|
||||
|
||||
flat@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||
@@ -1548,14 +1555,6 @@ flatbuffers@^1.12.0:
|
||||
resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.12.0.tgz#72e87d1726cb1b216e839ef02658aa87dcef68aa"
|
||||
integrity sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==
|
||||
|
||||
fluent-ffmpeg@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz#d6846be257777844249a4adeb320f25326d239f3"
|
||||
integrity sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==
|
||||
dependencies:
|
||||
async "^0.2.9"
|
||||
which "^1.1.1"
|
||||
|
||||
fn.name@1.x.x:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
||||
@@ -1669,6 +1668,11 @@ get-stream@^5.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
get-uri@^6.0.1:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.5.tgz#714892aa4a871db671abc5395e5e9447bc306a16"
|
||||
@@ -1812,6 +1816,11 @@ https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.6:
|
||||
agent-base "^7.1.2"
|
||||
debug "4"
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
humanize-duration@^3.25.1:
|
||||
version "3.33.2"
|
||||
resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.33.2.tgz#2e41986eabb00cb5ad0eef616a78233099dbdac4"
|
||||
@@ -2272,6 +2281,11 @@ merge-descriptors@1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||
|
||||
merge-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
@@ -2299,6 +2313,11 @@ mime@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
|
||||
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
mimic-response@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
||||
@@ -2484,6 +2503,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
npm-run-path@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
|
||||
@@ -2538,6 +2564,13 @@ one-time@^1.0.0:
|
||||
dependencies:
|
||||
fn.name "1.x.x"
|
||||
|
||||
onetime@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
onnx-proto@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/onnx-proto/-/onnx-proto-4.0.4.tgz#2431a25bee25148e915906dda0687aafe3b9e044"
|
||||
@@ -2705,7 +2738,7 @@ path-is-absolute@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
|
||||
|
||||
path-key@^3.1.0:
|
||||
path-key@^3.0.0, path-key@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||
@@ -3200,6 +3233,22 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
shell-env@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shell-env/-/shell-env-4.0.1.tgz#883302d9426095d398a39b102a851adb306b8cb8"
|
||||
integrity sha512-w3oeZ9qg/P6Lu6qqwavvMnB/bwfsz67gPB3WXmLd/n6zuh7TWQZtGa3iMEdmua0kj8rivkwl+vUjgLWlqZOMPw==
|
||||
dependencies:
|
||||
default-shell "^2.0.0"
|
||||
execa "^5.1.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
shell-path@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/shell-path/-/shell-path-3.1.0.tgz#950671fe15de70fb4d984b886d55e8a2f10bfe33"
|
||||
integrity sha512-s/9q9PEtcRmDTz69+cJ3yYBAe9yGrL7e46gm2bU4pQ9N48ecPK9QrGFnLwYgb4smOHskx4PL7wCNMktW2AoD+g==
|
||||
dependencies:
|
||||
shell-env "^4.0.1"
|
||||
|
||||
side-channel-list@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
|
||||
@@ -3240,6 +3289,11 @@ side-channel@^1.1.0:
|
||||
side-channel-map "^1.0.1"
|
||||
side-channel-weakmap "^1.0.2"
|
||||
|
||||
signal-exit@^3.0.3:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
@@ -3343,7 +3397,7 @@ streamx@^2.15.0, streamx@^2.21.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.1.2:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -3352,15 +3406,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
dependencies:
|
||||
eastasianwidth "^0.2.0"
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string_decoder@^1.1.1, string_decoder@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
@@ -3389,7 +3434,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba"
|
||||
integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
|
||||
@@ -3403,6 +3448,11 @@ strip-dirs@^2.0.0:
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
strip-final-newline@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
@@ -3746,13 +3796,6 @@ which-typed-array@^1.1.16:
|
||||
gopd "^1.2.0"
|
||||
has-tostringtag "^1.0.2"
|
||||
|
||||
which@^1.1.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
@@ -3795,7 +3838,7 @@ winston@^3.13.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
wrap-ansi@^7.0.0, wrap-ansi@^8.1.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -3804,15 +3847,6 @@ wrap-ansi@^7.0.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||
dependencies:
|
||||
ansi-styles "^6.1.0"
|
||||
string-width "^5.0.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
@@ -3886,6 +3920,11 @@ yauzl@^2.10.0, yauzl@^2.4.2:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
youtube-transcript-plus@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/youtube-transcript-plus/-/youtube-transcript-plus-1.1.2.tgz#f86851852a056088c11f4f6523ab0f8dba7d9711"
|
||||
integrity sha512-bLlqkA6gVVUorZpcc+THuECXyAwOpnHqW2lOav9g6gGovxAP3FCD8s9GBFVjmSl3cWWwwPPXtG/zY1nD+GvQ7A==
|
||||
|
||||
youtubei.js@^9.1.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/youtubei.js/-/youtubei.js-9.4.0.tgz#ccccaf4a295b96e3e17134a66730bbc82461594b"
|
||||
|
||||
@@ -162,6 +162,11 @@ GID='1000'
|
||||
# GITEE_AI_MODEL_PREF=
|
||||
# GITEE_AI_MODEL_TOKEN_LIMIT=
|
||||
|
||||
# LLM_PROVIDER='docker-model-runner'
|
||||
# DOCKER_MODEL_RUNNER_BASE_PATH='http://127.0.0.1:12434'
|
||||
# DOCKER_MODEL_RUNNER_LLM_MODEL_PREF='phi-3.5-mini'
|
||||
# DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT=4096
|
||||
|
||||
###########################################
|
||||
######## Embedding API SElECTION ##########
|
||||
###########################################
|
||||
|
||||
@@ -171,7 +171,7 @@ COPY --chown=anythingllm:anythingllm --from=frontend-build /app/frontend/dist /a
|
||||
# Setup the environment
|
||||
ENV NODE_ENV=production
|
||||
ENV ANYTHING_LLM_RUNTIME=docker
|
||||
ENV DEPLOYMENT_VERSION=1.9.1
|
||||
ENV DEPLOYMENT_VERSION=1.10.0
|
||||
|
||||
# Setup the healthcheck
|
||||
HEALTHCHECK --interval=1m --timeout=10s --start-period=1m \
|
||||
|
||||
@@ -93,7 +93,7 @@ ENV PUPPETEER_EXECUTABLE_PATH=/app/.cache/puppeteer/chrome/linux-119.0.6045.105/
|
||||
ENV NODE_ENV=production
|
||||
ENV ANYTHING_LLM_RUNTIME=docker
|
||||
ENV STORAGE_DIR=$STORAGE_DIR
|
||||
ENV DEPLOYMENT_VERSION=1.9.1
|
||||
ENV DEPLOYMENT_VERSION=1.10.0
|
||||
|
||||
# Expose the server port
|
||||
EXPOSE 3001
|
||||
|
||||
25
extras/support/announcements/2026-01-12.json
Normal file
25
extras/support/announcements/2026-01-12.json
Normal file
@@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"thumbnail_url": "https://cdn.anythingllm.com/support/announcements/assets/meeting-assistant.png",
|
||||
"title": "Meeting Assistant",
|
||||
"short_description": "Transcribe meetings and generate meeting notes entirely on device.",
|
||||
"goto": "https://docs.anythingllm.com/meeting-assistant/introduction",
|
||||
"author": "AnythingLLM",
|
||||
"date": "January 21, 2026"
|
||||
},
|
||||
{
|
||||
"thumbnail_url": "https://cdn.anythingllm.com/support/announcements/assets/mobile.png",
|
||||
"title": "AnythingLLM Mobile",
|
||||
"short_description": "AnythingLLM Mobile is now available on the Google Play Store.",
|
||||
"goto": "https://play.google.com/store/apps/details?id=com.anythingllm",
|
||||
"author": "AnythingLLM",
|
||||
"date": "January 5, 2026"
|
||||
},
|
||||
{
|
||||
"title": "50K Stars on Github",
|
||||
"short_description": "AnythingLLM broke 50K stars on Github!",
|
||||
"goto": "https://github.com/mintplex-labs/anything-llm",
|
||||
"author": "AnythingLLM",
|
||||
"date": "October 21, 2025"
|
||||
}
|
||||
]
|
||||
BIN
extras/support/announcements/assets/meeting-assistant.png
Normal file
BIN
extras/support/announcements/assets/meeting-assistant.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@@ -1,2 +1,3 @@
|
||||
2026-01-12.json
|
||||
2025-07-08.json
|
||||
2025-04-08.json
|
||||
46
frontend/eslint.config.js
Normal file
46
frontend/eslint.config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import js from "@eslint/js"
|
||||
import globals from "globals"
|
||||
import pluginReact from "eslint-plugin-react"
|
||||
import pluginReactHooks from "eslint-plugin-react-hooks"
|
||||
import pluginPrettier from "eslint-plugin-prettier"
|
||||
import configPrettier from "eslint-config-prettier"
|
||||
import { defineConfig } from "eslint/config"
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
ignores: ["**/*.min.js", "src/media/**/*"]
|
||||
},
|
||||
{
|
||||
files: ["src/**/*.{js,jsx}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser }
|
||||
},
|
||||
{
|
||||
files: ["src/**/*.{js,jsx}"],
|
||||
...pluginReact.configs.flat.recommended,
|
||||
plugins: {
|
||||
"react-hooks": pluginReactHooks,
|
||||
prettier: pluginPrettier
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
...configPrettier.rules,
|
||||
"prettier/prettier": "error",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"no-extra-boolean-cast": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off",
|
||||
"no-useless-escape": "off",
|
||||
"no-undef": "off",
|
||||
"no-unsafe-optional-chaining": "off",
|
||||
"no-constant-binary-expression": "off"
|
||||
}
|
||||
}
|
||||
])
|
||||
@@ -7,10 +7,12 @@
|
||||
"start": "vite --open",
|
||||
"dev": "cross-env NODE_ENV=development vite --debug --host=0.0.0.0",
|
||||
"build": "vite build && node scripts/postbuild.js",
|
||||
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./src",
|
||||
"lint:check": "eslint src",
|
||||
"lint": "eslint --fix src",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@lobehub/icons": "^4.0.3",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@mintplex-labs/piper-tts-web": "^1.0.4",
|
||||
"@phosphor-icons/react": "^2.1.7",
|
||||
@@ -35,6 +37,7 @@
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"react-i18next": "^14.1.1",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
@@ -51,6 +54,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@types/react": "^18.2.23",
|
||||
"@types/react-dom": "^18.2.8",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
@@ -58,16 +62,16 @@
|
||||
"autoprefixer": "^10.4.14",
|
||||
"buffer": "^6.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-ft-flow": "^3.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"flow-bin": "^0.217.0",
|
||||
"flow-remove-types": "^2.217.1",
|
||||
"globals": "^13.21.0",
|
||||
"globals": "^16.5.0",
|
||||
"hermes-eslint": "^0.15.0",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^3.0.3",
|
||||
@@ -75,4 +79,4 @@
|
||||
"tailwindcss": "^3.3.1",
|
||||
"vite": "^4.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Outlet, useLocation } from "react-router-dom";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { AuthProvider } from "@/AuthContext";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
@@ -12,25 +12,34 @@ import { FullScreenLoader } from "./components/Preloader";
|
||||
import { ThemeProvider } from "./ThemeContext";
|
||||
import { PWAModeProvider } from "./PWAContext";
|
||||
import KeyboardShortcutsHelp from "@/components/KeyboardShortcutsHelp";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
|
||||
|
||||
export default function App() {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<PWAModeProvider>
|
||||
<Suspense fallback={<FullScreenLoader />}>
|
||||
<AuthProvider>
|
||||
<LogoProvider>
|
||||
<PfpProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Outlet />
|
||||
<ToastContainer />
|
||||
<KeyboardShortcutsHelp />
|
||||
</I18nextProvider>
|
||||
</PfpProvider>
|
||||
</LogoProvider>
|
||||
</AuthProvider>
|
||||
</Suspense>
|
||||
</PWAModeProvider>
|
||||
</ThemeProvider>
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorBoundaryFallback}
|
||||
onError={console.error}
|
||||
resetKeys={[location.pathname]}
|
||||
>
|
||||
<ThemeProvider>
|
||||
<PWAModeProvider>
|
||||
<Suspense fallback={<FullScreenLoader />}>
|
||||
<AuthProvider>
|
||||
<LogoProvider>
|
||||
<PfpProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Outlet />
|
||||
<ToastContainer />
|
||||
<KeyboardShortcutsHelp />
|
||||
</I18nextProvider>
|
||||
</PfpProvider>
|
||||
</LogoProvider>
|
||||
</AuthProvider>
|
||||
</Suspense>
|
||||
</PWAModeProvider>
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import React, { useState, createContext } from "react";
|
||||
import React, { useState, createContext, useEffect } from "react";
|
||||
import {
|
||||
AUTH_TIMESTAMP,
|
||||
AUTH_TOKEN,
|
||||
AUTH_USER,
|
||||
USER_PROMPT_INPUT_MAP,
|
||||
} from "@/utils/constants";
|
||||
import System from "./models/system";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export const AuthContext = createContext(null);
|
||||
export function AuthProvider(props) {
|
||||
const localUser = localStorage.getItem(AUTH_USER);
|
||||
const localAuthToken = localStorage.getItem(AUTH_TOKEN);
|
||||
const [store, setStore] = useState({
|
||||
user: localUser ? JSON.parse(localUser) : null,
|
||||
user: localUser ? safeJsonParse(localUser, null) : null,
|
||||
authToken: localAuthToken ? localAuthToken : null,
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
/* NOTE:
|
||||
* 1. There's no reason for these helper functions to be stateful. They could
|
||||
* just be regular funcs or methods on a basic object.
|
||||
* 2. These actions are not being invoked anywhere in the
|
||||
* codebase, dead code.
|
||||
*/
|
||||
const [actions] = useState({
|
||||
updateUser: (user, authToken = "") => {
|
||||
localStorage.setItem(AUTH_USER, JSON.stringify(user));
|
||||
@@ -30,6 +41,36 @@ export function AuthProvider(props) {
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* On initial mount and whenever the token changes, fetch a new user object
|
||||
* If the user is suspended, (success === false and data === null) logout the user and redirect to the login page
|
||||
* If success is true and data is not null, update the user object in the store (multi-user mode only)
|
||||
* If success is true and data is null, do nothing (single-user mode only) with or without password protection
|
||||
*/
|
||||
useEffect(() => {
|
||||
async function refreshUser() {
|
||||
const { success, user: refreshedUser } = await System.refreshUser();
|
||||
if (success && refreshedUser === null) return;
|
||||
|
||||
if (!success) {
|
||||
localStorage.removeItem(AUTH_USER);
|
||||
localStorage.removeItem(AUTH_TOKEN);
|
||||
localStorage.removeItem(AUTH_TIMESTAMP);
|
||||
localStorage.removeItem(USER_PROMPT_INPUT_MAP);
|
||||
setStore({ user: null, authToken: null });
|
||||
navigate("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(AUTH_USER, JSON.stringify(refreshedUser));
|
||||
setStore((prev) => ({
|
||||
...prev,
|
||||
user: refreshedUser,
|
||||
}));
|
||||
}
|
||||
if (store.authToken) refreshUser();
|
||||
}, [store.authToken]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ store, actions }}>
|
||||
{props.children}
|
||||
|
||||
@@ -14,6 +14,8 @@ export default function OllamaEmbeddingOptions({ settings }) {
|
||||
showAdvancedControls,
|
||||
setShowAdvancedControls,
|
||||
handleAutoDetectClick,
|
||||
authToken,
|
||||
authTokenValue,
|
||||
} = useProviderEndpointAutoDiscovery({
|
||||
provider: "ollama",
|
||||
initialBasePath: settings?.EmbeddingBasePath,
|
||||
@@ -48,13 +50,13 @@ export default function OllamaEmbeddingOptions({ settings }) {
|
||||
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">
|
||||
Maximum length of text chunks, in characters, for embedding.
|
||||
</Tooltip>
|
||||
@@ -134,13 +136,13 @@ export default function OllamaEmbeddingOptions({ settings }) {
|
||||
data-tooltip-id="ollama-batch-size-tooltip"
|
||||
className="flex gap-x-1 items-center mb-3"
|
||||
>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Embedding batch size
|
||||
</label>
|
||||
<Info
|
||||
size={16}
|
||||
className="text-theme-text-secondary cursor-pointer"
|
||||
/>
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Embedding batch size
|
||||
</label>
|
||||
<Tooltip id="ollama-batch-size-tooltip">
|
||||
Number of text chunks to embed in parallel. Higher values
|
||||
improve speed but use more memory. Default is 1.
|
||||
@@ -163,6 +165,31 @@ export default function OllamaEmbeddingOptions({ settings }) {
|
||||
faster embedding.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-white font-semibold block mb-3 text-sm">
|
||||
Auth Token (optional)
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="OllamaLLMAuthToken"
|
||||
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="Enter your Auth Token"
|
||||
defaultValue={settings?.OllamaLLMAuthToken ? "*".repeat(20) : ""}
|
||||
value={authTokenValue.value}
|
||||
onChange={authToken.onChange}
|
||||
onBlur={authToken.onBlur}
|
||||
required={false}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2">
|
||||
Enter a <code>Bearer</code> Auth Token for interacting with your
|
||||
Ollama server.
|
||||
<br />
|
||||
Used <b>only</b> if running Ollama behind an authentication
|
||||
server.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
93
frontend/src/components/ErrorBoundaryFallback/index.jsx
Normal file
93
frontend/src/components/ErrorBoundaryFallback/index.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { House, ArrowClockwise, Copy, Check } from "@phosphor-icons/react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ErrorBoundaryFallback({ error, resetErrorBoundary }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyErrorDetails = async () => {
|
||||
const details = {
|
||||
url: window.location.href,
|
||||
error: error?.name || "Unknown Error",
|
||||
message: error?.message || "No message available",
|
||||
stack: error?.stack || "No stack trace available",
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const formattedDetails = `
|
||||
Error Report
|
||||
============
|
||||
Timestamp: ${details.timestamp}
|
||||
URL: ${details.url}
|
||||
User Agent: ${details.userAgent}
|
||||
|
||||
Error: ${details.error}
|
||||
Message: ${details.message}
|
||||
|
||||
Stack Trace:
|
||||
${details.stack}
|
||||
`.trim();
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(formattedDetails);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy error details:", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-theme-bg-primary text-theme-text-primary gap-4 p-4 md:p-8 w-full">
|
||||
<h1 className="text-xl md:text-2xl font-bold text-center">
|
||||
An error occurred.
|
||||
</h1>
|
||||
<p className="text-theme-text-secondary text-center px-4">
|
||||
{error?.message}
|
||||
</p>
|
||||
{import.meta.env.DEV && (
|
||||
<div className="w-full max-w-4xl">
|
||||
<div className="flex justify-end mb-2">
|
||||
<button
|
||||
onClick={copyErrorDetails}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-theme-bg-secondary text-theme-text-primary rounded hover:bg-theme-sidebar-item-hover transition-all duration-200 text-xs font-medium"
|
||||
title="Copy error details"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-3.5 h-3.5" weight="bold" />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
Copy Details
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="w-full text-xs md:text-sm text-theme-text-secondary bg-theme-bg-secondary p-4 md:p-6 rounded-lg overflow-x-auto overflow-y-auto max-h-[60vh] md:max-h-[70vh] whitespace-pre-wrap break-words font-mono border border-theme-border shadow-sm">
|
||||
{error?.stack}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col md:flex-row gap-3 md:gap-4 mt-4 w-full md:w-auto">
|
||||
<button
|
||||
onClick={resetErrorBoundary}
|
||||
className="flex items-center justify-center gap-2 px-4 py-2 bg-theme-bg-secondary text-theme-text-primary rounded-lg hover:bg-theme-sidebar-item-hover transition-all duration-300 w-full md:w-auto"
|
||||
>
|
||||
<ArrowClockwise className="w-4 h-4" />
|
||||
Reset
|
||||
</button>
|
||||
<NavLink
|
||||
to="/"
|
||||
className="flex items-center justify-center gap-2 px-4 py-2 bg-theme-bg-secondary text-theme-text-primary rounded-lg hover:bg-theme-sidebar-item-hover transition-all duration-300 w-full md:w-auto"
|
||||
>
|
||||
<House className="w-4 h-4" />
|
||||
Home
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export default function DellProAIStudioOptions({
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-2">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import System from "@/models/system";
|
||||
import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery";
|
||||
import { CircleNotch, Info } from "@phosphor-icons/react";
|
||||
import strDistance from "js-levenshtein";
|
||||
import { LLM_PREFERENCE_CHANGED_EVENT } from "@/pages/GeneralSettings/LLMPreference";
|
||||
import { DOCKER_MODEL_RUNNER_COMMON_URLS } from "@/utils/constants";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { Link } from "react-router-dom";
|
||||
import ModelTable from "@/components/lib/ModelTable";
|
||||
import ModelTableLayout from "@/components/lib/ModelTable/layout";
|
||||
import ModelTableLoadingSkeleton from "@/components/lib/ModelTable/loading";
|
||||
import DMRUtils from "@/models/utils/dmrUtils";
|
||||
import showToast from "@/utils/toast";
|
||||
|
||||
export default function DockerModelRunnerOptions({ settings }) {
|
||||
const {
|
||||
autoDetecting: loading,
|
||||
basePath,
|
||||
basePathValue,
|
||||
handleAutoDetectClick,
|
||||
} = useProviderEndpointAutoDiscovery({
|
||||
provider: "docker-model-runner",
|
||||
initialBasePath: settings?.DockerModelRunnerBasePath,
|
||||
ENDPOINTS: DOCKER_MODEL_RUNNER_COMMON_URLS,
|
||||
});
|
||||
const [selectedModelId, setSelectedModelId] = useState(
|
||||
settings?.DockerModelRunnerModelPref
|
||||
);
|
||||
const [maxTokens, setMaxTokens] = useState(
|
||||
settings?.DockerModelRunnerModelTokenLimit || 4096
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-y-7">
|
||||
<div className="flex gap-[36px] mt-1.5 flex-wrap">
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<label className="text-white text-sm font-semibold">
|
||||
Base URL
|
||||
</label>
|
||||
{loading ? (
|
||||
<CircleNotch className="w-4 h-4 text-theme-text-secondary animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
{!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>
|
||||
<Tooltip
|
||||
id="docker-model-runner-base-url"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
delayHide={800}
|
||||
clickable={true}
|
||||
className="tooltip !text-xs !opacity-100 z-99"
|
||||
style={{
|
||||
maxWidth: "250px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
Enter the URL where the Docker Model Runner is running.
|
||||
<br />
|
||||
<br />
|
||||
You <b>must</b> have enabled the Docker Model Runner TCP support
|
||||
for this to work.
|
||||
<br />
|
||||
<br />
|
||||
<Link
|
||||
to="https://docs.docker.com/ai/model-runner/get-started/#docker-desktop"
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
Learn more →
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="text-theme-text-secondary cursor-pointer hover:bg-theme-bg-primary flex items-center justify-center rounded-full"
|
||||
data-tooltip-id="docker-model-runner-base-url"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-delay-hide={800}
|
||||
>
|
||||
<Info size={18} className="text-theme-text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="url"
|
||||
name="DockerModelRunnerBasePath"
|
||||
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="http://localhost:12434/engines/llama.cpp/v1"
|
||||
value={basePathValue.value}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
onChange={basePath.onChange}
|
||||
onBlur={basePath.onBlur}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
<label className="text-white text-sm font-semibold block">
|
||||
Model context window
|
||||
</label>
|
||||
<Tooltip
|
||||
id="docker-model-runner-model-context-window"
|
||||
place="top"
|
||||
delayShow={300}
|
||||
delayHide={800}
|
||||
clickable={true}
|
||||
className="tooltip !text-xs !opacity-100 z-99"
|
||||
style={{
|
||||
maxWidth: "350px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
The maximum number of tokens that can be used for a model context
|
||||
window.
|
||||
<br />
|
||||
<br />
|
||||
To set the context window limit for a model, you can use the{" "}
|
||||
<code>docker run</code> command with the{" "}
|
||||
<code>--context-window</code> parameter.
|
||||
<br />
|
||||
<br />
|
||||
<code>
|
||||
docker model configure --context-size {maxTokens || 8192}{" "}
|
||||
{selectedModelId ?? "ai/qwen3:latest"}
|
||||
</code>
|
||||
<br />
|
||||
<br />
|
||||
<Link
|
||||
to="https://docs.docker.com/ai/model-runner/#context-size"
|
||||
target="_blank"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
Learn more →
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="text-theme-text-secondary cursor-pointer hover:bg-theme-bg-primary flex items-center justify-center rounded-full"
|
||||
data-tooltip-id="docker-model-runner-model-context-window"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-delay-hide={800}
|
||||
>
|
||||
<Info size={18} className="text-theme-text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
name="DockerModelRunnerModelTokenLimit"
|
||||
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="4096"
|
||||
min={1}
|
||||
value={maxTokens}
|
||||
onChange={(e) => setMaxTokens(Number(e.target.value))}
|
||||
onScroll={(e) => e.target.blur()}
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<DockerModelRunnerModelSelection
|
||||
selectedModelId={selectedModelId}
|
||||
setSelectedModelId={setSelectedModelId}
|
||||
basePath={basePathValue.value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DockerModelRunnerModelSelection({
|
||||
selectedModelId,
|
||||
setSelectedModelId,
|
||||
basePath = null,
|
||||
}) {
|
||||
const [customModels, setCustomModels] = useState([]);
|
||||
const [filteredModels, setFilteredModels] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
async function fetchModels() {
|
||||
if (!basePath) {
|
||||
setCustomModels([]);
|
||||
setFilteredModels([]);
|
||||
setLoading(false);
|
||||
setSearchQuery("");
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const { models } = await System.customModels(
|
||||
"docker-model-runner",
|
||||
null,
|
||||
basePath
|
||||
);
|
||||
setCustomModels(models || []);
|
||||
setFilteredModels(models || []);
|
||||
setSearchQuery("");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchModels();
|
||||
}, [basePath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchQuery || !customModels.length) {
|
||||
setFilteredModels(customModels || []);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedSearchQuery = searchQuery.toLowerCase().trim();
|
||||
const filteredModels = new Map();
|
||||
|
||||
customModels.forEach((model) => {
|
||||
const modelNameNormalized = model.name.toLowerCase();
|
||||
const modelOrganizationNormalized = model.organization.toLowerCase();
|
||||
|
||||
if (modelNameNormalized.startsWith(normalizedSearchQuery))
|
||||
filteredModels.set(model.id, model);
|
||||
if (modelOrganizationNormalized.startsWith(normalizedSearchQuery))
|
||||
filteredModels.set(model.id, model);
|
||||
if (strDistance(modelNameNormalized, normalizedSearchQuery) <= 2)
|
||||
filteredModels.set(model.id, model);
|
||||
if (strDistance(modelOrganizationNormalized, normalizedSearchQuery) <= 2)
|
||||
filteredModels.set(model.id, model);
|
||||
});
|
||||
|
||||
setFilteredModels(Array.from(filteredModels.values()));
|
||||
}, [searchQuery]);
|
||||
|
||||
async function downloadModel(modelId, fileSize, progressCallback) {
|
||||
try {
|
||||
if (
|
||||
!window.confirm(
|
||||
`Are you sure you want to download this model? It is ${fileSize} in size and may take a while to download.`
|
||||
)
|
||||
)
|
||||
return;
|
||||
const { success, error } = await DMRUtils.downloadModel(
|
||||
modelId,
|
||||
basePath,
|
||||
progressCallback
|
||||
);
|
||||
if (!success)
|
||||
throw new Error(
|
||||
error || "An error occurred while downloading the model"
|
||||
);
|
||||
progressCallback(100);
|
||||
handleSetActiveModel(modelId);
|
||||
|
||||
const existingModels = [...customModels];
|
||||
const newModel = existingModels.find((model) => model.id === modelId);
|
||||
if (newModel) {
|
||||
newModel.downloaded = true;
|
||||
setCustomModels(existingModels);
|
||||
setFilteredModels(existingModels);
|
||||
setSearchQuery("");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error downloading model:", e);
|
||||
showToast(
|
||||
e.message || "An error occurred while downloading the model",
|
||||
"error",
|
||||
{ clear: true }
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function groupModelsByAlias(models) {
|
||||
const mapping = new Map();
|
||||
mapping.set("installed", new Map());
|
||||
mapping.set("not installed", new Map());
|
||||
|
||||
const groupedModels = models.reduce((acc, model) => {
|
||||
acc[model.organization] = acc[model.organization] || [];
|
||||
acc[model.organization].push(model);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.entries(groupedModels).forEach(([organization, models]) => {
|
||||
const hasInstalled = models.some((model) => model.downloaded);
|
||||
if (hasInstalled) {
|
||||
const installedModels = models.filter((model) => model.downloaded);
|
||||
mapping
|
||||
.get("installed")
|
||||
.set("Downloaded Models", [
|
||||
...(mapping.get("installed").get("Downloaded Models") || []),
|
||||
...installedModels,
|
||||
]);
|
||||
}
|
||||
const tags = models.map((model) => ({
|
||||
...model,
|
||||
name: model.name.split(":")[1],
|
||||
}));
|
||||
mapping.get("not installed").set(organization, tags);
|
||||
});
|
||||
|
||||
const orderedMap = new Map();
|
||||
const installedMap = new Map();
|
||||
mapping
|
||||
.get("installed")
|
||||
.entries()
|
||||
.forEach(([organization, models]) =>
|
||||
installedMap.set(organization, models)
|
||||
);
|
||||
mapping
|
||||
.get("not installed")
|
||||
.entries()
|
||||
.forEach(([organization, models]) =>
|
||||
orderedMap.set(organization, models)
|
||||
);
|
||||
|
||||
// Sort the models by organization/creator name alphabetically but keep the installed models at the top
|
||||
return Object.fromEntries(
|
||||
Array.from(installedMap.entries())
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.concat(
|
||||
Array.from(orderedMap.entries()).sort((a, b) =>
|
||||
a[0].localeCompare(b[0])
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function handleSetActiveModel(modelId) {
|
||||
if (modelId === selectedModelId) return;
|
||||
setSelectedModelId(modelId);
|
||||
window.dispatchEvent(new Event(LLM_PREFERENCE_CHANGED_EVENT));
|
||||
}
|
||||
|
||||
const groupedModels = groupModelsByAlias(filteredModels);
|
||||
return (
|
||||
<ModelTableLayout
|
||||
fetchModels={fetchModels}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
loading={loading}
|
||||
>
|
||||
<Tooltip
|
||||
id="docker-model-runner-install-model-tooltip"
|
||||
place="top"
|
||||
className="tooltip !text-xs !opacity-100 z-99"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="DockerModelRunnerModelPref"
|
||||
id="DockerModelRunnerModelPref"
|
||||
value={selectedModelId}
|
||||
/>
|
||||
{loading ? (
|
||||
<ModelTableLoadingSkeleton />
|
||||
) : filteredModels.length === 0 ? (
|
||||
<div className="flex flex-col w-full gap-y-2 mt-4">
|
||||
<p className="text-theme-text-secondary text-sm">No models found!</p>
|
||||
</div>
|
||||
) : (
|
||||
Object.entries(groupedModels).map(([alias, models]) => (
|
||||
<ModelTable
|
||||
key={alias}
|
||||
alias={alias}
|
||||
models={models}
|
||||
setActiveModel={handleSetActiveModel}
|
||||
downloadModel={downloadModel}
|
||||
selectedModelId={selectedModelId}
|
||||
ui={{
|
||||
showRuntime: false,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</ModelTableLayout>
|
||||
);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export default function FoundryOptions({ settings }) {
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Token Context Window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function GenericOpenAiOptions({ settings }) {
|
||||
<div className="flex gap-[36px] flex-wrap">
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function GiteeAIOptions({ settings }) {
|
||||
<GiteeAIModelSelection settings={settings} />
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-2">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function KoboldCPPOptions({ settings }) {
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-2">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function LiteLLMOptions({ settings }) {
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function LocalAiOptions({ settings, showAlert = false }) {
|
||||
/>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-2">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function TextGenWebUIOptions({ settings }) {
|
||||
</div>
|
||||
<div className="flex flex-col w-60">
|
||||
<label className="text-white text-sm font-semibold block mb-3">
|
||||
Token context window
|
||||
Model context window
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
384
frontend/src/components/ProviderPrivacy/constants.js
Normal file
384
frontend/src/components/ProviderPrivacy/constants.js
Normal file
@@ -0,0 +1,384 @@
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import OpenAiLogo from "@/media/llmprovider/openai.png";
|
||||
import GenericOpenAiLogo from "@/media/llmprovider/generic-openai.png";
|
||||
import AzureOpenAiLogo from "@/media/llmprovider/azure.png";
|
||||
import AnthropicLogo from "@/media/llmprovider/anthropic.png";
|
||||
import GeminiLogo from "@/media/llmprovider/gemini.png";
|
||||
import OllamaLogo from "@/media/llmprovider/ollama.png";
|
||||
import TogetherAILogo from "@/media/llmprovider/togetherai.png";
|
||||
import FireworksAILogo from "@/media/llmprovider/fireworksai.jpeg";
|
||||
import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
|
||||
import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
||||
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
||||
import HuggingFaceLogo from "@/media/llmprovider/huggingface.png";
|
||||
import PerplexityLogo from "@/media/llmprovider/perplexity.png";
|
||||
import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg";
|
||||
import NovitaLogo from "@/media/llmprovider/novita.png";
|
||||
import GroqLogo from "@/media/llmprovider/groq.png";
|
||||
import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
|
||||
import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
|
||||
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
|
||||
import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
|
||||
import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
|
||||
import APIPieLogo from "@/media/llmprovider/apipie.png";
|
||||
import XAILogo from "@/media/llmprovider/xai.png";
|
||||
import ZAiLogo from "@/media/llmprovider/zai.png";
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import ChromaLogo from "@/media/vectordbs/chroma.png";
|
||||
import PineconeLogo from "@/media/vectordbs/pinecone.png";
|
||||
import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
||||
import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
||||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||
import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
|
||||
import PPIOLogo from "@/media/llmprovider/ppio.png";
|
||||
import PGVectorLogo from "@/media/vectordbs/pgvector.png";
|
||||
import DPAISLogo from "@/media/llmprovider/dpais.png";
|
||||
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";
|
||||
import CometApiLogo from "@/media/llmprovider/cometapi.png";
|
||||
import FoundryLogo from "@/media/llmprovider/foundry-local.png";
|
||||
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
|
||||
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
|
||||
|
||||
const LLM_PROVIDER_PRIVACY_MAP = {
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
policyUrl: "https://openai.com/policies/privacy-policy/",
|
||||
logo: OpenAiLogo,
|
||||
},
|
||||
azure: {
|
||||
name: "Azure OpenAI",
|
||||
policyUrl: "https://privacy.microsoft.com/privacystatement",
|
||||
logo: AzureOpenAiLogo,
|
||||
},
|
||||
anthropic: {
|
||||
name: "Anthropic",
|
||||
policyUrl: "https://www.anthropic.com/privacy",
|
||||
logo: AnthropicLogo,
|
||||
},
|
||||
gemini: {
|
||||
name: "Google Gemini",
|
||||
policyUrl: "https://policies.google.com/privacy",
|
||||
logo: GeminiLogo,
|
||||
},
|
||||
"nvidia-nim": {
|
||||
name: "NVIDIA NIM",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running the NVIDIA NIM.",
|
||||
],
|
||||
logo: NvidiaNimLogo,
|
||||
},
|
||||
lmstudio: {
|
||||
name: "LMStudio",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LMStudio.",
|
||||
],
|
||||
logo: LMStudioLogo,
|
||||
},
|
||||
localai: {
|
||||
name: "LocalAI",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LocalAI.",
|
||||
],
|
||||
logo: LocalAiLogo,
|
||||
},
|
||||
ollama: {
|
||||
name: "Ollama",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running Ollama models.",
|
||||
],
|
||||
logo: OllamaLogo,
|
||||
},
|
||||
togetherai: {
|
||||
name: "TogetherAI",
|
||||
policyUrl: "https://www.together.ai/privacy",
|
||||
logo: TogetherAILogo,
|
||||
},
|
||||
fireworksai: {
|
||||
name: "FireworksAI",
|
||||
policyUrl: "https://fireworks.ai/privacy-policy",
|
||||
logo: FireworksAILogo,
|
||||
},
|
||||
mistral: {
|
||||
name: "Mistral",
|
||||
policyUrl: "https://legal.mistral.ai/terms/privacy-policy",
|
||||
logo: MistralLogo,
|
||||
},
|
||||
huggingface: {
|
||||
name: "HuggingFace",
|
||||
policyUrl: "https://huggingface.co/privacy",
|
||||
logo: HuggingFaceLogo,
|
||||
},
|
||||
perplexity: {
|
||||
name: "Perplexity AI",
|
||||
policyUrl: "https://www.perplexity.ai/privacy",
|
||||
logo: PerplexityLogo,
|
||||
},
|
||||
openrouter: {
|
||||
name: "OpenRouter",
|
||||
policyUrl: "https://openrouter.ai/privacy",
|
||||
logo: OpenRouterLogo,
|
||||
},
|
||||
novita: {
|
||||
name: "Novita AI",
|
||||
policyUrl: "https://novita.ai/legal/privacy-policy",
|
||||
logo: NovitaLogo,
|
||||
},
|
||||
groq: {
|
||||
name: "Groq",
|
||||
policyUrl: "https://groq.com/privacy-policy/",
|
||||
logo: GroqLogo,
|
||||
},
|
||||
koboldcpp: {
|
||||
name: "KoboldCPP",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running KoboldCPP",
|
||||
],
|
||||
logo: KoboldCPPLogo,
|
||||
},
|
||||
textgenwebui: {
|
||||
name: "Oobabooga Web UI",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running the Oobabooga Text Generation Web UI",
|
||||
],
|
||||
logo: TextGenWebUILogo,
|
||||
},
|
||||
"generic-openai": {
|
||||
name: "Generic OpenAI compatible service",
|
||||
description: [
|
||||
"Data is shared according to the terms of service applicable with your generic endpoint provider.",
|
||||
],
|
||||
logo: GenericOpenAiLogo,
|
||||
},
|
||||
cohere: {
|
||||
name: "Cohere",
|
||||
policyUrl: "https://cohere.com/privacy",
|
||||
logo: CohereLogo,
|
||||
},
|
||||
litellm: {
|
||||
name: "LiteLLM",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LiteLLM",
|
||||
],
|
||||
logo: LiteLLMLogo,
|
||||
},
|
||||
bedrock: {
|
||||
name: "AWS Bedrock",
|
||||
policyUrl: "https://aws.amazon.com/bedrock/security-compliance/",
|
||||
logo: AWSBedrockLogo,
|
||||
},
|
||||
deepseek: {
|
||||
name: "DeepSeek",
|
||||
policyUrl:
|
||||
"https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html",
|
||||
logo: DeepSeekLogo,
|
||||
},
|
||||
apipie: {
|
||||
name: "APIpie.AI",
|
||||
policyUrl: "https://apipie.ai/docs/Terms/privacy",
|
||||
logo: APIPieLogo,
|
||||
},
|
||||
xai: {
|
||||
name: "xAI",
|
||||
policyUrl: "https://x.ai/legal/privacy-policy",
|
||||
logo: XAILogo,
|
||||
},
|
||||
zai: {
|
||||
name: "Z.AI",
|
||||
policyUrl: "https://docs.z.ai/legal-agreement/privacy-policy",
|
||||
logo: ZAiLogo,
|
||||
},
|
||||
ppio: {
|
||||
name: "PPIO",
|
||||
policyUrl: "https://www.pipio.ai/privacy-policy",
|
||||
logo: PPIOLogo,
|
||||
},
|
||||
dpais: {
|
||||
name: "Dell Pro AI Studio",
|
||||
description: [
|
||||
"Your model and chat contents are only accessible on the computer running Dell Pro AI Studio.",
|
||||
],
|
||||
logo: DPAISLogo,
|
||||
},
|
||||
moonshotai: {
|
||||
name: "Moonshot AI",
|
||||
policyUrl: "https://platform.moonshot.ai/docs/agreement/userprivacy",
|
||||
logo: MoonshotAiLogo,
|
||||
},
|
||||
cometapi: {
|
||||
name: "CometAPI",
|
||||
policyUrl: "https://apidoc.cometapi.com/privacy-policy-873819m0",
|
||||
logo: CometApiLogo,
|
||||
},
|
||||
foundry: {
|
||||
name: "Microsoft Foundry Local",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running Foundry Local.",
|
||||
],
|
||||
logo: FoundryLogo,
|
||||
},
|
||||
giteeai: {
|
||||
name: "GiteeAI",
|
||||
policyUrl: "https://ai.gitee.com/docs/appendix/privacy",
|
||||
logo: GiteeAILogo,
|
||||
},
|
||||
"docker-model-runner": {
|
||||
name: "Docker Model Runner",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running Docker Model Runner.",
|
||||
],
|
||||
logo: DockerModelRunnerLogo,
|
||||
},
|
||||
};
|
||||
|
||||
const VECTOR_DB_PROVIDER_PRIVACY_MAP = {
|
||||
pgvector: {
|
||||
name: "PGVector",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your PostgreSQL instance.",
|
||||
"Access to your instance is managed by you.",
|
||||
],
|
||||
logo: PGVectorLogo,
|
||||
},
|
||||
chroma: {
|
||||
name: "Chroma",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Chroma instance.",
|
||||
"Access to your instance is managed by you.",
|
||||
],
|
||||
logo: ChromaLogo,
|
||||
},
|
||||
chromacloud: {
|
||||
name: "Chroma Cloud",
|
||||
policyUrl: "https://www.trychroma.com/privacy",
|
||||
logo: ChromaLogo,
|
||||
},
|
||||
pinecone: {
|
||||
name: "Pinecone",
|
||||
policyUrl: "https://www.pinecone.io/privacy/",
|
||||
logo: PineconeLogo,
|
||||
},
|
||||
qdrant: {
|
||||
name: "Qdrant",
|
||||
policyUrl: "https://qdrant.tech/legal/privacy-policy/",
|
||||
logo: QDrantLogo,
|
||||
},
|
||||
weaviate: {
|
||||
name: "Weaviate",
|
||||
policyUrl: "https://weaviate.io/privacy",
|
||||
logo: WeaviateLogo,
|
||||
},
|
||||
milvus: {
|
||||
name: "Milvus",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Milvus instance (cloud or self-hosted).",
|
||||
],
|
||||
logo: MilvusLogo,
|
||||
},
|
||||
zilliz: {
|
||||
name: "Zilliz Cloud",
|
||||
policyUrl: "https://zilliz.com/privacy-policy",
|
||||
logo: ZillizLogo,
|
||||
},
|
||||
astra: {
|
||||
name: "AstraDB",
|
||||
policyUrl: "https://www.ibm.com/us-en/privacy",
|
||||
logo: AstraDBLogo,
|
||||
},
|
||||
lancedb: {
|
||||
name: "LanceDB",
|
||||
description: [
|
||||
"Your vectors and document text are stored privately on this instance of AnythingLLM.",
|
||||
],
|
||||
logo: LanceDbLogo,
|
||||
},
|
||||
};
|
||||
|
||||
const EMBEDDING_ENGINE_PROVIDER_PRIVACY_MAP = {
|
||||
native: {
|
||||
name: "AnythingLLM Embedder",
|
||||
description: [
|
||||
"Your document text is embedded privately on this instance of AnythingLLM.",
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
},
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
policyUrl: "https://openai.com/policies/privacy-policy/",
|
||||
logo: OpenAiLogo,
|
||||
},
|
||||
azure: {
|
||||
name: "Azure OpenAI",
|
||||
policyUrl: "https://privacy.microsoft.com/privacystatement",
|
||||
logo: AzureOpenAiLogo,
|
||||
},
|
||||
localai: {
|
||||
name: "LocalAI",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running LocalAI.",
|
||||
],
|
||||
logo: LocalAiLogo,
|
||||
},
|
||||
ollama: {
|
||||
name: "Ollama",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running Ollama.",
|
||||
],
|
||||
logo: OllamaLogo,
|
||||
},
|
||||
lmstudio: {
|
||||
name: "LMStudio",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running LMStudio.",
|
||||
],
|
||||
logo: LMStudioLogo,
|
||||
},
|
||||
openrouter: {
|
||||
name: "OpenRouter",
|
||||
policyUrl: "https://openrouter.ai/privacy",
|
||||
logo: OpenRouterLogo,
|
||||
},
|
||||
cohere: {
|
||||
name: "Cohere",
|
||||
policyUrl: "https://cohere.com/privacy",
|
||||
logo: CohereLogo,
|
||||
},
|
||||
voyageai: {
|
||||
name: "Voyage AI",
|
||||
policyUrl: "https://www.voyageai.com/privacy",
|
||||
logo: VoyageAiLogo,
|
||||
},
|
||||
mistral: {
|
||||
name: "Mistral AI",
|
||||
policyUrl: "https://legal.mistral.ai/terms/privacy-policy",
|
||||
logo: MistralLogo,
|
||||
},
|
||||
litellm: {
|
||||
name: "LiteLLM",
|
||||
description: [
|
||||
"Your document text is only accessible on the server running LiteLLM and to the providers you configured in LiteLLM.",
|
||||
],
|
||||
logo: LiteLLMLogo,
|
||||
},
|
||||
"generic-openai": {
|
||||
name: "Generic OpenAI compatible service",
|
||||
description: [
|
||||
"Data is shared according to the terms of service applicable with your generic endpoint provider.",
|
||||
],
|
||||
logo: GenericOpenAiLogo,
|
||||
},
|
||||
gemini: {
|
||||
name: "Google Gemini",
|
||||
policyUrl: "https://policies.google.com/privacy",
|
||||
logo: GeminiLogo,
|
||||
},
|
||||
};
|
||||
|
||||
export const PROVIDER_PRIVACY_MAP = {
|
||||
llm: LLM_PROVIDER_PRIVACY_MAP,
|
||||
embeddingEngine: EMBEDDING_ENGINE_PROVIDER_PRIVACY_MAP,
|
||||
vectorDb: VECTOR_DB_PROVIDER_PRIVACY_MAP,
|
||||
};
|
||||
119
frontend/src/components/ProviderPrivacy/index.jsx
Normal file
119
frontend/src/components/ProviderPrivacy/index.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import System from "@/models/system";
|
||||
import { PROVIDER_PRIVACY_MAP } from "./constants";
|
||||
import { ArrowSquareOut } from "@phosphor-icons/react";
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import { Link } from "react-router-dom";
|
||||
import { titleCase, sentenceCase } from "text-case";
|
||||
|
||||
function defaultProvider(providerString) {
|
||||
return {
|
||||
name: providerString
|
||||
? titleCase(sentenceCase(String(providerString)))
|
||||
: "Unknown",
|
||||
description: [
|
||||
`"${providerString}" has no known data handling policy defined in AnythingLLM.`,
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ProviderPrivacy() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [providers, setProviders] = useState({
|
||||
llmProvider: null,
|
||||
embeddingEngine: null,
|
||||
vectorDb: null,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchProviders() {
|
||||
const _settings = await System.keys();
|
||||
const providerDefinition =
|
||||
PROVIDER_PRIVACY_MAP.llm[_settings?.LLMProvider] ||
|
||||
defaultProvider(_settings?.LLMProvider);
|
||||
const embeddingEngineDefinition =
|
||||
PROVIDER_PRIVACY_MAP.embeddingEngine[_settings?.EmbeddingEngine] ||
|
||||
defaultProvider(_settings?.EmbeddingEngine);
|
||||
const vectorDbDefinition =
|
||||
PROVIDER_PRIVACY_MAP.vectorDb[_settings?.VectorDB] ||
|
||||
defaultProvider(_settings?.VectorDB);
|
||||
|
||||
setProviders({
|
||||
llmProvider: providerDefinition,
|
||||
embeddingEngine: embeddingEngineDefinition,
|
||||
vectorDb: vectorDbDefinition,
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
fetchProviders();
|
||||
}, []);
|
||||
|
||||
if (loading) return null;
|
||||
return (
|
||||
<div className="flex flex-col gap-8 w-full max-w-2xl">
|
||||
<ProviderPrivacyItem
|
||||
title="LLM Provider"
|
||||
provider={providers.llmProvider}
|
||||
altText="LLM Logo"
|
||||
/>
|
||||
<ProviderPrivacyItem
|
||||
title="Embedding Preference"
|
||||
provider={providers.embeddingEngine}
|
||||
altText="Embedding Logo"
|
||||
/>
|
||||
<ProviderPrivacyItem
|
||||
title="Vector Database"
|
||||
provider={providers.vectorDb}
|
||||
altText="Vector DB Logo"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderPrivacyItem({ title, provider, altText }) {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-y-3 pb-4 border-b border-theme-sidebar-border">
|
||||
<div className="text-theme-text-primary text-base font-bold">{title}</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<img
|
||||
src={provider.logo}
|
||||
alt={altText}
|
||||
className="w-8 h-8 rounded flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-theme-text-primary text-sm font-semibold">
|
||||
{provider.name}
|
||||
</span>
|
||||
</div>
|
||||
{provider.policyUrl ? (
|
||||
<div className="text-theme-text-secondary text-sm">
|
||||
Your usage, chats, and data are subject to the service's{" "}
|
||||
<Link
|
||||
className="text-theme-text-secondary hover:text-theme-text-primary text-sm font-medium underline transition-colors inline-flex items-center gap-1"
|
||||
to={provider.policyUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
privacy policy
|
||||
<ArrowSquareOut size={12} />
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
) : (
|
||||
provider.description && (
|
||||
<ul className="flex flex-col list-none gap-1">
|
||||
{provider.description.map((desc, idx) => (
|
||||
<li key={idx} className="text-theme-text-secondary text-sm">
|
||||
{desc}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CaretRight } from "@phosphor-icons/react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function MenuOption({
|
||||
btnText,
|
||||
@@ -130,7 +131,7 @@ function useIsExpanded({
|
||||
if (hasVisibleChildren) {
|
||||
const storedValue = localStorage.getItem(storageKey);
|
||||
if (storedValue !== null) {
|
||||
return JSON.parse(storedValue);
|
||||
return safeJsonParse(storedValue, false);
|
||||
}
|
||||
return childOptions.some((child) => child.href === location);
|
||||
}
|
||||
|
||||
@@ -386,6 +386,12 @@ const SidebarOptions = ({ user = null, t }) => (
|
||||
flex: true,
|
||||
roles: ["admin", "manager"],
|
||||
},
|
||||
{
|
||||
btnText: t("settings.mobile-app"),
|
||||
href: paths.settings.mobile(),
|
||||
flex: true,
|
||||
roles: ["admin"],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Option
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTheme } from "@/hooks/useTheme";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function AccountModal({ user, hideModal }) {
|
||||
const { pfp, setPfp } = usePfp();
|
||||
@@ -54,7 +55,7 @@ export default function AccountModal({ user, hideModal }) {
|
||||
|
||||
const { success, error } = await System.updateUser(data);
|
||||
if (success) {
|
||||
let storedUser = JSON.parse(localStorage.getItem(AUTH_USER));
|
||||
let storedUser = safeJsonParse(localStorage.getItem(AUTH_USER), null);
|
||||
if (storedUser) {
|
||||
storedUser.username = data.username;
|
||||
storedUser.bio = data.bio;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { formatDateTimeAsMoment } from "@/utils/directories";
|
||||
import { numberWithCommas } from "@/utils/numbers";
|
||||
import React, { useEffect, useState, useContext } from "react";
|
||||
const MetricsContext = React.createContext();
|
||||
@@ -41,6 +42,26 @@ function getAutoShowMetrics() {
|
||||
return window?.localStorage?.getItem(SHOW_METRICS_KEY) === "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the metrics string for a given metrics object
|
||||
* - Model name
|
||||
* - Duration and output TPS
|
||||
* - Timestamp
|
||||
* @param {metrics: {duration:number, outputTps: number, model?: string, timestamp?: number}} metrics
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildMetricsString(metrics = {}) {
|
||||
return [
|
||||
metrics?.model ? metrics.model : "",
|
||||
`${formatDuration(metrics.duration)} (${formatTps(metrics.outputTps)} tok/s)`,
|
||||
metrics?.timestamp
|
||||
? formatDateTimeAsMoment(metrics.timestamp, "MMM D, h:mm A")
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the show metrics setting in localStorage `anythingllm_show_chat_metrics` key
|
||||
* @returns {void}
|
||||
@@ -88,7 +109,7 @@ export function MetricsProvider({ children }) {
|
||||
|
||||
/**
|
||||
* Render the metrics for a given chat, if available
|
||||
* @param {metrics: {duration:number, outputTps: number}} props
|
||||
* @param {metrics: {duration:number, outputTps: number, model: string, timestamp: number}} props
|
||||
* @returns
|
||||
*/
|
||||
export default function RenderMetrics({ metrics = {} }) {
|
||||
@@ -110,8 +131,7 @@ export default function RenderMetrics({ metrics = {} }) {
|
||||
className={`border-none flex justify-end items-center gap-x-[8px] ${showMetricsAutomatically ? "opacity-100" : "opacity-0"} md:group-hover:opacity-100 transition-all duration-300`}
|
||||
>
|
||||
<p className="cursor-pointer text-xs font-mono text-theme-text-secondary opacity-50">
|
||||
{formatDuration(metrics.duration)} ({formatTps(metrics.outputTps)}{" "}
|
||||
tok/s)
|
||||
{buildMetricsString(metrics)}
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
|
||||
359
frontend/src/components/lib/ModelTable/index.jsx
Normal file
359
frontend/src/components/lib/ModelTable/index.jsx
Normal file
@@ -0,0 +1,359 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import {
|
||||
CaretDown,
|
||||
CaretRight,
|
||||
Cpu,
|
||||
Circle,
|
||||
DotsThreeVertical,
|
||||
CloudArrowDown,
|
||||
CircleNotch,
|
||||
} from "@phosphor-icons/react";
|
||||
import pluralize from "pluralize";
|
||||
import { titleCase } from "text-case";
|
||||
import { humanFileSize } from "@/utils/numbers";
|
||||
import MonoProviderIcon from "../MonoProviderIcon";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModelDefinition
|
||||
* @property {string} id - The ID of the model.
|
||||
* @property {'CPU' | 'GPU' | 'NPU'} deviceType - The device type of the model.
|
||||
* @property {number} modelSize - The size of the model in megabytes.
|
||||
* @property {boolean} downloaded - Whether the model is downloaded.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {object} props - The props of the component.
|
||||
* @param {string} props.alias - The alias of the model.
|
||||
* @param {Array<ModelDefinition>} props.models - The models to display.
|
||||
* @param {(model: string, progressCallback: (percentage: number) => void) => void} props.downloadModel - The function to download the model.
|
||||
* @param {(model: string) => void} props.uninstallModel - The function to uninstall the model.
|
||||
* @param {(model: string) => void} props.setActiveModel - The function to set the active model.
|
||||
* @param {string} props.selectedModelId - The ID of the selected model.
|
||||
* @param {object} props.ui - The UI configuration.
|
||||
* @param {boolean} props.ui.showRuntime - Whether to show the runtime.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
export default function ModelTable({
|
||||
alias = "",
|
||||
models = [],
|
||||
downloadModel = null,
|
||||
uninstallModel = null,
|
||||
setActiveModel = () => {},
|
||||
selectedModelId = "",
|
||||
ui = {
|
||||
showRuntime: true,
|
||||
},
|
||||
}) {
|
||||
const [showAll, setShowAll] = useState(
|
||||
models.some((model) => model.downloaded)
|
||||
);
|
||||
const totalModels = models.length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full border-b border-theme-modal-border py-[18px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
className="border-none text-theme-text-secondary text-sm font-medium hover:underline flex items-center gap-x-[8px]"
|
||||
>
|
||||
{showAll ? (
|
||||
<CaretDown
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="text-theme-text-secondary"
|
||||
/>
|
||||
) : (
|
||||
<CaretRight
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="text-theme-text-secondary"
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center gap-x-[4px]">
|
||||
<MonoProviderIcon
|
||||
provider={alias}
|
||||
match="pattern"
|
||||
size={16}
|
||||
className="text-theme-text-primary"
|
||||
/>
|
||||
<p className="flex items-center gap-x-1 text-theme-text-primary text-base font-bold">
|
||||
{titleCase(alias)}
|
||||
<span className="text-theme-text-secondary font-normal text-sm">
|
||||
({totalModels} {pluralize("Model", totalModels)})
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<div hidden={!showAll} className="mt-[16px]">
|
||||
<div className="w-full flex flex-col gap-y-[8px]">
|
||||
{models.map((model) => (
|
||||
<ModelRow
|
||||
key={model.id}
|
||||
alias={alias}
|
||||
model={model}
|
||||
downloadModel={downloadModel}
|
||||
uninstallModel={uninstallModel}
|
||||
setActiveModel={setActiveModel}
|
||||
selectedModelId={selectedModelId}
|
||||
ui={ui}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{deviceType: ModelDefinition["deviceType"]}} deviceType
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function DeviceTypeTag({ deviceType }) {
|
||||
const Wrapper = ({ text, bgClass, textClass }) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
bgClass + " px-1.5 py-1 rounded-full flex items-center gap-x-1 w-fit"
|
||||
}
|
||||
>
|
||||
<Cpu size={14} weight="bold" className={textClass} />
|
||||
<p className={textClass + " text-xs"}>{text}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
switch (deviceType?.toLowerCase()) {
|
||||
case "cpu":
|
||||
return (
|
||||
<Wrapper
|
||||
text="CPU"
|
||||
bgClass="bg-zinc-800 light:bg-zinc-200"
|
||||
textClass="text-theme-text-primary"
|
||||
/>
|
||||
);
|
||||
case "gpu":
|
||||
return (
|
||||
<Wrapper
|
||||
text="GPU"
|
||||
bgClass="bg-green-800 light:bg-green-200"
|
||||
textClass="text-theme-text-primary"
|
||||
/>
|
||||
);
|
||||
case "npu":
|
||||
return (
|
||||
<Wrapper
|
||||
text="NPU"
|
||||
bgClass="bg-indigo-800 light:bg-indigo-200"
|
||||
textClass="text-theme-text-primary"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Wrapper
|
||||
text="CPU"
|
||||
bgClass="bg-zinc-800 light:bg-zinc-200"
|
||||
textClass="text-theme-text-primary"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} props - The props of the component.
|
||||
* @param {ModelDefinition} props.model - The model to display.
|
||||
* @param {(model: string, progressCallback: (percentage: number) => void) => Promise<void>} props.downloadModel - The function to download the model.
|
||||
* @param {(model: string) => Promise<void>} props.uninstallModel - The function to uninstall the model.
|
||||
* @param {(model: string) => void} props.setActiveModel - The function to set the active model.
|
||||
* @param {string} props.selectedModelId - The ID of the selected model.
|
||||
* @param {object} props.ui - The UI configuration.
|
||||
* @param {boolean} props.ui.showRuntime - Whether to show the runtime.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function ModelRow({
|
||||
alias,
|
||||
model,
|
||||
downloadModel = null,
|
||||
uninstallModel = null,
|
||||
setActiveModel,
|
||||
selectedModelId,
|
||||
ui = {
|
||||
showRuntime: true,
|
||||
},
|
||||
}) {
|
||||
const modelRowRef = useRef(null);
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [downloadPercentage, setDownloadPercentage] = useState(0);
|
||||
const fileSize =
|
||||
typeof model.size === "number"
|
||||
? humanFileSize(model.size * 1e6, true, 2)
|
||||
: (model.size ?? "Unknown size");
|
||||
const [isActiveModel, setIsActiveModel] = useState(
|
||||
selectedModelId === model.id
|
||||
);
|
||||
|
||||
async function handleSetActiveModel() {
|
||||
setDownloadPercentage(0);
|
||||
if (model.downloaded) setActiveModel(model.id);
|
||||
else {
|
||||
try {
|
||||
if (!downloadModel) return;
|
||||
setProcessing(true);
|
||||
await downloadModel(model.id, fileSize, (percentage) => {
|
||||
setDownloadPercentage(percentage);
|
||||
});
|
||||
} catch {
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUninstallModel() {
|
||||
if (!uninstallModel) return;
|
||||
try {
|
||||
setProcessing(true);
|
||||
await uninstallModel(model.id);
|
||||
} catch {
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedModelId === model.id) {
|
||||
setIsActiveModel(true);
|
||||
modelRowRef.current.classList.add("!bg-gray-200/10");
|
||||
setTimeout(
|
||||
() => modelRowRef.current.classList.remove("!bg-gray-200/10"),
|
||||
800
|
||||
);
|
||||
} else {
|
||||
setIsActiveModel(false);
|
||||
}
|
||||
}, [selectedModelId]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={modelRowRef}
|
||||
className="w-full grid grid-cols-[1fr_auto_1fr] items-center gap-x-4 transition-all duration-300 rounded-lg"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="border-none flex items-center gap-x-[8px] whitespace-nowrap py-[8px]"
|
||||
disabled={processing}
|
||||
onClick={handleSetActiveModel}
|
||||
>
|
||||
{ui.showRuntime && <DeviceTypeTag deviceType={model.deviceType} />}
|
||||
{!ui.showRuntime &&
|
||||
model.downloaded &&
|
||||
alias === "Downloaded Models" && (
|
||||
<MonoProviderIcon
|
||||
provider={model.organization}
|
||||
match="pattern"
|
||||
size={16}
|
||||
className="text-theme-text-primary"
|
||||
/>
|
||||
)}
|
||||
<p className="text-theme-text-primary text-base">{model.name}</p>
|
||||
<p className="text-theme-text-secondary opacity-70 text-base">
|
||||
{fileSize}
|
||||
</p>
|
||||
</button>
|
||||
|
||||
<div className="justify-self-start">
|
||||
<RenderStatus model={model} isActiveModel={isActiveModel} />
|
||||
</div>
|
||||
|
||||
<div className="relative justify-self-end">
|
||||
{uninstallModel && model.downloaded ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="border-none hover:bg-white/20 rounded-lg p-1"
|
||||
onClick={() => setShowOptions(!showOptions)}
|
||||
>
|
||||
<DotsThreeVertical
|
||||
size={22}
|
||||
weight="bold"
|
||||
className="text-theme-text-primary cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
{showOptions && (
|
||||
<div className="absolute top-[20px] right-[20px] bg-theme-action-menu-bg border border-theme-modal-border rounded-lg py-2 px-4 shadow-lg">
|
||||
<button
|
||||
type="button"
|
||||
className="border-none font-medium group"
|
||||
onClick={handleUninstallModel}
|
||||
>
|
||||
<p className="text-sm text-theme-text-primary group-hover:underline group-hover:text-theme-text-secondary">
|
||||
Uninstall
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{!model.downloaded && !processing && (
|
||||
<button
|
||||
type="button"
|
||||
data-tooltip-id="docker-model-runner-install-model-tooltip"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-delay-show={300}
|
||||
data-tooltip-content={`Install ${model.organization}:${model.name}`}
|
||||
className="border-none hover:bg-white/20 light:hover:bg-black/5 rounded-lg p-2 flex items-center gap-x-1 cursor-pointer"
|
||||
onClick={handleSetActiveModel}
|
||||
>
|
||||
<CloudArrowDown
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="text-theme-text-primary"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{!model.downloaded && processing && (
|
||||
<div className="flex items-center justify-center gap-x-[10px] whitespace-nowrap">
|
||||
{!downloadPercentage && (
|
||||
<CircleNotch
|
||||
size={16}
|
||||
weight="bold"
|
||||
className="text-theme-text-primary animate-spin"
|
||||
/>
|
||||
)}
|
||||
<p className="text-theme-text-secondary text-sm">
|
||||
{downloadPercentage}%
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderStatus({ model, isActiveModel }) {
|
||||
if (isActiveModel) {
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-x-[10px] whitespace-nowrap">
|
||||
<Circle size={8} weight="fill" className="text-green-500" />
|
||||
<p className="text-theme-text-primary text-sm">Active</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isActiveModel && model.downloaded) {
|
||||
return (
|
||||
<p className="text-theme-text-secondary text-sm italic whitespace-nowrap">
|
||||
Installed
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (!model.downloaded) {
|
||||
return (
|
||||
<p className="text-theme-text-secondary text-sm italic whitespace-nowrap">
|
||||
Not Installed
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
80
frontend/src/components/lib/ModelTable/layout.jsx
Normal file
80
frontend/src/components/lib/ModelTable/layout.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ArrowClockwise,
|
||||
CircleNotch,
|
||||
MagnifyingGlass,
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
export default function ModelTableLayout({
|
||||
children,
|
||||
fetchModels = null,
|
||||
searchQuery = "",
|
||||
setSearchQuery = () => {},
|
||||
loading = false,
|
||||
}) {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
async function refreshModels() {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await fetchModels?.();
|
||||
} catch {
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex gap-x-2 items-center pb-[8px]">
|
||||
<label className="text-theme-text-primary text-base font-semibold">
|
||||
Available Models
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-x-[16px]">
|
||||
<div className="relative flex-1 flex-grow">
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
weight="bold"
|
||||
color="var(--theme-text-primary)"
|
||||
className="absolute left-[9px] top-[10px] text-theme-settings-input-placeholder peer-focus:invisible"
|
||||
/>
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search models"
|
||||
value={searchQuery}
|
||||
disabled={loading}
|
||||
className="min-h-[32px] 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 pl-[30px] py-2 search-input disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSearchQuery(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!!fetchModels && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={refreshModels}
|
||||
disabled={isRefreshing || loading}
|
||||
className="border-none text-theme-text-secondary text-sm font-medium hover:bg-white/10 light:hover:bg-black/5 rounded-lg px-2 h-full flex items-center gap-x-1 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<CircleNotch className="w-4 h-4 text-theme-text-secondary animate-spin" />
|
||||
) : (
|
||||
<ArrowClockwise
|
||||
weight="bold"
|
||||
className="w-4 h-4 text-theme-text-secondary"
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={`text-sm font-medium ${isRefreshing ? "hidden" : "text-theme-text-secondary"}`}
|
||||
>
|
||||
Refresh Models
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
frontend/src/components/lib/ModelTable/loading.jsx
Normal file
18
frontend/src/components/lib/ModelTable/loading.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Skeleton from "react-loading-skeleton";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
|
||||
export default function ModelTableLoadingSkeleton() {
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-y-4 pt-4">
|
||||
<Skeleton.default
|
||||
height={100}
|
||||
width="100%"
|
||||
count={7}
|
||||
highlightColor="var(--theme-settings-input-active)"
|
||||
baseColor="var(--theme-settings-input-bg)"
|
||||
enableAnimation={true}
|
||||
containerClassName="w-fill flex gap-[8px] flex-col p-0"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
103
frontend/src/components/lib/MonoProviderIcon/index.jsx
Normal file
103
frontend/src/components/lib/MonoProviderIcon/index.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
// https://lobehub.com/icons for all the icons
|
||||
import OpenAI from "@lobehub/icons/es/OpenAI/components/Mono";
|
||||
import Anthropic from "@lobehub/icons/es/Anthropic/components/Mono";
|
||||
import Google from "@lobehub/icons/es/Google/components/Mono";
|
||||
import Gemma from "@lobehub/icons/es/Gemma/components/Mono";
|
||||
import Gemini from "@lobehub/icons/es/Gemini/components/Mono";
|
||||
import Microsoft from "@lobehub/icons/es/Microsoft/components/Mono";
|
||||
import Meta from "@lobehub/icons/es/Meta/components/Mono";
|
||||
import Mistral from "@lobehub/icons/es/Mistral/components/Mono";
|
||||
import Azure from "@lobehub/icons/es/Azure/components/Mono";
|
||||
import AzureAI from "@lobehub/icons/es/AzureAI/components/Mono";
|
||||
import DeepSeek from "@lobehub/icons/es/DeepSeek/components/Mono";
|
||||
import HuggingFace from "@lobehub/icons/es/HuggingFace/components/Mono";
|
||||
import Qwen from "@lobehub/icons/es/Qwen/components/Mono";
|
||||
import IBM from "@lobehub/icons/es/IBM/components/Mono";
|
||||
import Bytedance from "@lobehub/icons/es/ByteDance/components/Mono";
|
||||
import Kimi from "@lobehub/icons/es/Kimi/components/Mono";
|
||||
import Snowflake from "@lobehub/icons/es/Snowflake/components/Mono";
|
||||
|
||||
// Direct provider key -> icon mapping for exact matches
|
||||
const providerIcons = {
|
||||
openai: OpenAI,
|
||||
anthropic: Anthropic,
|
||||
google: Google,
|
||||
microsoft: Microsoft,
|
||||
gemma: Gemma,
|
||||
gemini: Gemini,
|
||||
meta: Meta,
|
||||
mistral: Mistral,
|
||||
azure: Azure,
|
||||
azureai: AzureAI,
|
||||
deepseek: DeepSeek,
|
||||
huggingface: HuggingFace,
|
||||
qwen: Qwen,
|
||||
qwq: Qwen,
|
||||
ibm: IBM,
|
||||
bytedance: Bytedance,
|
||||
kimi: Kimi,
|
||||
};
|
||||
|
||||
// Pattern matching rules: regex pattern -> icon component
|
||||
// These are checked in order, first match wins
|
||||
const modelPatterns = [
|
||||
{ pattern: /^gpt/i, icon: OpenAI },
|
||||
{ pattern: /^o\d+/i, icon: OpenAI }, // o1, o3, etc.
|
||||
{ pattern: /^claude-/i, icon: Anthropic },
|
||||
{ pattern: /^gemini-/i, icon: Gemini },
|
||||
{ pattern: /gemma/i, icon: Gemma },
|
||||
{ pattern: /llama/i, icon: Meta },
|
||||
{ pattern: /^meta/i, icon: Meta },
|
||||
{
|
||||
pattern: /^(mistral|devstral|mixtral|magistral|codestral|ministral)/i,
|
||||
icon: Mistral,
|
||||
},
|
||||
{ pattern: /^deepseek/i, icon: DeepSeek },
|
||||
{ pattern: /^qwen/i, icon: Qwen },
|
||||
{ pattern: /^qwq/i, icon: Qwen },
|
||||
{ pattern: /^phi/i, icon: Microsoft },
|
||||
{ pattern: /^granite/i, icon: IBM },
|
||||
{ pattern: /^doubao/i, icon: Bytedance },
|
||||
{ pattern: /^moonshot/i, icon: Kimi },
|
||||
{ pattern: /^smol/i, icon: HuggingFace },
|
||||
{ pattern: /^seed/i, icon: Bytedance },
|
||||
{ pattern: /^kimi/i, icon: Kimi },
|
||||
{ pattern: /^snowflake/i, icon: Snowflake },
|
||||
];
|
||||
|
||||
/**
|
||||
* Find icon by matching model name against known patterns
|
||||
* @param {string} modelName - The model name to match
|
||||
* @returns {React.ComponentType|null}
|
||||
*/
|
||||
function findIconByModelName(modelName) {
|
||||
if (!modelName) return null;
|
||||
const match = modelPatterns.find(({ pattern }) => pattern.test(modelName));
|
||||
return match?.icon || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} props - The props of the component.
|
||||
* @param {string} props.provider - The provider key (for exact match) or model name (for pattern match).
|
||||
* @param {('exact'|'pattern')} props.match - Match mode: 'exact' for provider key, 'pattern' for model name matching.
|
||||
* @param {number} props.size - The size of the icon.
|
||||
* @param {string} props.className - The class name of the icon.
|
||||
* @param {string} props.fallbackIconKey - The key of the fallback icon to use if no icon is found.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
export default function MonoProviderIcon({
|
||||
provider,
|
||||
match = "exact",
|
||||
size = 24,
|
||||
className = "",
|
||||
fallbackIconKey = null,
|
||||
}) {
|
||||
let Icon = null;
|
||||
|
||||
if (match === "exact") Icon = providerIcons[provider?.toLowerCase()];
|
||||
else if (match === "pattern") Icon = findIconByModelName(provider);
|
||||
if (!Icon && fallbackIconKey && providerIcons[fallbackIconKey])
|
||||
Icon = providerIcons[fallbackIconKey];
|
||||
if (!Icon) return null;
|
||||
return <Icon size={size} className={className} />;
|
||||
}
|
||||
@@ -52,6 +52,7 @@ const groupedProviders = [
|
||||
"novita",
|
||||
"openrouter",
|
||||
"ppio",
|
||||
"docker-model-runner",
|
||||
];
|
||||
export default function useGetProviderModels(provider = null) {
|
||||
const [defaultModels, setDefaultModels] = useState([]);
|
||||
|
||||
@@ -43,7 +43,6 @@ export default function usePromptInputStorage({
|
||||
// Notify parent component so message state is synchronized
|
||||
onChange({ target: { value: userPromptInputValue } });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const debouncedWriteToStorage = useMemo(
|
||||
|
||||
@@ -101,6 +101,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
1085
frontend/src/locales/cs/common.js
Normal file
1085
frontend/src/locales/cs/common.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -103,6 +103,7 @@ const TRANSLATIONS = {
|
||||
contact: "Support kontaktieren",
|
||||
"browser-extension": "Browser-Extension",
|
||||
"system-prompt-variables": "Systempromptvariablen",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -112,6 +112,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Experimental Features",
|
||||
contact: "Contact Support",
|
||||
"browser-extension": "Browser Extension",
|
||||
"mobile-app": "AnythingLLM Mobile",
|
||||
},
|
||||
|
||||
// Page Definitions
|
||||
@@ -715,7 +716,7 @@ const TRANSLATIONS = {
|
||||
title: "Privacy & Data-Handling",
|
||||
description:
|
||||
"This is your configuration for how connected third party providers and AnythingLLM handle your data.",
|
||||
llm: "LLM Selection",
|
||||
llm: "LLM Provider",
|
||||
embedding: "Embedding Preference",
|
||||
vector: "Vector Database",
|
||||
anonymous: "Anonymous Telemetry Enabled",
|
||||
|
||||
@@ -103,6 +103,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Funciones experimentales",
|
||||
contact: "Contactar con soporte",
|
||||
"browser-extension": "Extensión del navegador",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -101,6 +101,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Eksperimentaalsed funktsioonid",
|
||||
contact: "Tugi",
|
||||
"browser-extension": "Brauserilaiend",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -94,6 +94,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -102,6 +102,7 @@ const TRANSLATIONS = {
|
||||
interface: "Interface",
|
||||
branding: "Personnalisation",
|
||||
chat: "Chat",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -100,6 +100,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "תכונות ניסיוניות",
|
||||
contact: "צור קשר עם התמיכה",
|
||||
"browser-extension": "תוסף דפדפן",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -94,6 +94,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -102,6 +102,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -101,6 +101,7 @@ const TRANSLATIONS = {
|
||||
interface: "UI 환경 설정",
|
||||
branding: "브랜딩 및 화이트라벨링",
|
||||
chat: "채팅",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -102,6 +102,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Eksperimentālās funkcijas",
|
||||
contact: "Sazināties ar atbalstu",
|
||||
"browser-extension": "Pārlūka paplašinājums",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -94,6 +94,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -103,6 +103,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Funkcje eksperymentalne",
|
||||
contact: "Kontakt z pomocą techniczną",
|
||||
"browser-extension": "Rozszerzenie przeglądarki",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -101,6 +101,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Recursos Experimentais",
|
||||
contact: "Suporte",
|
||||
"browser-extension": "Extensão de Navegador",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -36,6 +36,7 @@ import Japanese from "./ja/common.js";
|
||||
import Lativian from "./lv/common.js";
|
||||
import Polish from "./pl/common.js";
|
||||
import Romanian from "./ro/common.js";
|
||||
import Czech from "./cs/common.js";
|
||||
|
||||
export const defaultNS = "common";
|
||||
export const resources = {
|
||||
@@ -105,4 +106,7 @@ export const resources = {
|
||||
ro: {
|
||||
common: Romanian,
|
||||
},
|
||||
cs: {
|
||||
common: Czech,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -103,6 +103,7 @@ const TRANSLATIONS = {
|
||||
"experimental-features": "Funcții experimentale",
|
||||
contact: "Contact suport",
|
||||
"browser-extension": "Extensie browser",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -102,6 +102,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -94,6 +94,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -94,6 +94,7 @@ const TRANSLATIONS = {
|
||||
interface: null,
|
||||
branding: null,
|
||||
chat: null,
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -98,6 +98,7 @@ const TRANSLATIONS = {
|
||||
contact: "联系支持",
|
||||
"browser-extension": "浏览器扩展",
|
||||
"system-prompt-variables": "系统提示变量",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -98,6 +98,7 @@ const TRANSLATIONS = {
|
||||
interface: "使用者介面偏好設定",
|
||||
branding: "品牌與白標設定",
|
||||
chat: "聊天室",
|
||||
"mobile-app": null,
|
||||
},
|
||||
login: {
|
||||
"multi-user": {
|
||||
|
||||
@@ -372,6 +372,14 @@ const router = createBrowserRouter([
|
||||
return { element: <ManagerRoute Component={MobileConnections} /> };
|
||||
},
|
||||
},
|
||||
// Catch-all route for 404s
|
||||
{
|
||||
path: "*",
|
||||
lazy: async () => {
|
||||
const { default: NotFound } = await import("@/pages/404");
|
||||
return { element: <NotFound /> };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
BIN
frontend/src/media/llmprovider/docker-model-runner.png
Normal file
BIN
frontend/src/media/llmprovider/docker-model-runner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
@@ -1,4 +1,5 @@
|
||||
import { APPEARANCE_SETTINGS } from "@/utils/constants";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
/**
|
||||
* @typedef { 'showScrollbar' |
|
||||
@@ -23,12 +24,8 @@ const Appearance = {
|
||||
* @returns {{showScrollbar: boolean, autoSubmitSttInput: boolean, autoPlayAssistantTtsResponse: boolean, enableSpellCheck: boolean, renderHTML: boolean}}
|
||||
*/
|
||||
getSettings: () => {
|
||||
try {
|
||||
const settings = localStorage.getItem(APPEARANCE_SETTINGS);
|
||||
return settings ? JSON.parse(settings) : Appearance.defaultSettings;
|
||||
} catch (e) {
|
||||
return Appearance.defaultSettings;
|
||||
}
|
||||
const settings = localStorage.getItem(APPEARANCE_SETTINGS);
|
||||
return safeJsonParse(settings, Appearance.defaultSettings);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,22 @@ const System = {
|
||||
return { valid: false, message: e.message };
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Refreshes the user object from the session.
|
||||
* @returns {Promise<{success: boolean, user: Object | null, message: string | null}>}
|
||||
*/
|
||||
refreshUser: () => {
|
||||
return fetch(`${API_BASE}/system/refresh-user`, {
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error("Could not refresh user.");
|
||||
return res.json();
|
||||
})
|
||||
.catch((e) => {
|
||||
return { success: false, user: null, message: e.message };
|
||||
});
|
||||
},
|
||||
recoverAccount: async function (username, recoveryCodes) {
|
||||
return await fetch(`${API_BASE}/system/recover-account`, {
|
||||
method: "POST",
|
||||
|
||||
77
frontend/src/models/utils/dmrUtils.js
Normal file
77
frontend/src/models/utils/dmrUtils.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import { API_BASE } from "@/utils/constants";
|
||||
import { baseHeaders } from "@/utils/request";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
const DMRUtils = {
|
||||
/**
|
||||
* Download a DMR model.
|
||||
* @param {string} modelId - The ID of the model to download.
|
||||
* @param {(percentage: number) => void} progressCallback - The callback to receive the progress percentage. If the model is already downloaded, it will be called once with 100.
|
||||
* @returns {Promise<{success: boolean, error: string|null}>}
|
||||
*/
|
||||
downloadModel: async function (
|
||||
modelId,
|
||||
basePath = "",
|
||||
progressCallback = () => {}
|
||||
) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/utils/dmr/download-model`, {
|
||||
method: "POST",
|
||||
headers: baseHeaders(),
|
||||
body: JSON.stringify({ modelId, basePath }),
|
||||
});
|
||||
|
||||
if (!response.ok)
|
||||
throw new Error("Error downloading model: " + response.statusText);
|
||||
const reader = response.body.getReader();
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
const { value, done: readerDone } = await reader.read();
|
||||
if (readerDone) {
|
||||
done = true;
|
||||
resolve({ success: true });
|
||||
} else {
|
||||
const chunk = new TextDecoder("utf-8").decode(value);
|
||||
const lines = chunk.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data:")) {
|
||||
const data = safeJsonParse(line.slice(5));
|
||||
switch (data?.type) {
|
||||
case "success":
|
||||
done = true;
|
||||
resolve({ success: true });
|
||||
break;
|
||||
case "error":
|
||||
done = true;
|
||||
resolve({
|
||||
success: false,
|
||||
error: data?.error || data?.message,
|
||||
});
|
||||
break;
|
||||
case "progress":
|
||||
progressCallback(data?.percentage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error downloading model:", error);
|
||||
resolve({
|
||||
success: false,
|
||||
error:
|
||||
error?.message || "An error occurred while downloading the model",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
// Uninstall a DMR model is not supported via the API
|
||||
};
|
||||
|
||||
export default DMRUtils;
|
||||
@@ -185,10 +185,8 @@ const Workspace = {
|
||||
}
|
||||
},
|
||||
async onmessage(msg) {
|
||||
try {
|
||||
const chatResult = JSON.parse(msg.data);
|
||||
handleChat(chatResult);
|
||||
} catch {}
|
||||
const chatResult = safeJsonParse(msg.data, null);
|
||||
if (chatResult) handleChat(chatResult);
|
||||
},
|
||||
onerror(err) {
|
||||
handleChat({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ABORT_STREAM_EVENT } from "@/utils/chat";
|
||||
import { API_BASE } from "@/utils/constants";
|
||||
import { baseHeaders } from "@/utils/request";
|
||||
import { baseHeaders, safeJsonParse } from "@/utils/request";
|
||||
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
@@ -144,10 +144,8 @@ const WorkspaceThread = {
|
||||
}
|
||||
},
|
||||
async onmessage(msg) {
|
||||
try {
|
||||
const chatResult = JSON.parse(msg.data);
|
||||
handleChat(chatResult);
|
||||
} catch {}
|
||||
const chatResult = safeJsonParse(msg.data, null);
|
||||
if (chatResult) handleChat(chatResult);
|
||||
},
|
||||
onerror(err) {
|
||||
handleChat({
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { House, MagnifyingGlass } from "@phosphor-icons/react";
|
||||
|
||||
export default function Contact() {
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="text-black">
|
||||
<Header />
|
||||
<div className="flex flex-col justify-center mx-auto mt-52 text-center max-w-2x1">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-black md:text-5xl">
|
||||
404 – Unavailable
|
||||
</h1>
|
||||
<br />
|
||||
<a
|
||||
className="w-64 p-1 mx-auto font-bold text-center text-black border border-gray-500 rounded-lg sm:p-4"
|
||||
href="/"
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-theme-bg-primary text-theme-text-primary gap-4 p-4 md:p-8 w-full">
|
||||
<MagnifyingGlass className="w-16 h-16 text-theme-text-secondary" />
|
||||
<h1 className="text-xl md:text-2xl font-bold text-center">
|
||||
404 - Page Not Found
|
||||
</h1>
|
||||
<p className="text-theme-text-secondary text-center px-4">
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
<div className="flex flex-col md:flex-row gap-3 md:gap-4 mt-4 w-full md:w-auto">
|
||||
<NavLink
|
||||
to="/"
|
||||
className="flex items-center justify-center gap-2 px-4 py-2 bg-theme-bg-secondary text-theme-text-primary rounded-lg hover:bg-theme-sidebar-item-hover transition-all duration-300 w-full md:w-auto"
|
||||
>
|
||||
Return Home
|
||||
</a>
|
||||
<House className="w-4 h-4" />
|
||||
Go Home
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="mt-64"></div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,8 +53,7 @@ const SEARCH_PROVIDERS = [
|
||||
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.",
|
||||
description: "Web search powered by a custom Google Search Engine.",
|
||||
},
|
||||
{
|
||||
name: "SerpApi",
|
||||
|
||||
@@ -7,10 +7,4 @@ export const configurableFeatures = {
|
||||
component: LiveSyncToggle,
|
||||
key: "experimental_live_file_sync",
|
||||
},
|
||||
experimental_mobile_connections: {
|
||||
title: "AnythingLLM Mobile",
|
||||
href: paths.settings.mobileConnections(),
|
||||
key: "experimental_mobile_connections",
|
||||
autoEnabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CaretDown, CaretUp } from "@phosphor-icons/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function LogRow({ log }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
@@ -8,11 +9,9 @@ export default function LogRow({ log }) {
|
||||
|
||||
useEffect(() => {
|
||||
function parseAndSetMetadata() {
|
||||
try {
|
||||
let data = JSON.parse(log.metadata);
|
||||
setHasMetadata(Object.keys(data)?.length > 0);
|
||||
setMetadata(data);
|
||||
} catch {}
|
||||
const data = safeJsonParse(log.metadata, {});
|
||||
setHasMetadata(Object.keys(data)?.length > 0);
|
||||
setMetadata(data);
|
||||
}
|
||||
parseAndSetMetadata();
|
||||
}, [log.metadata]);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../../NewEmbedModal";
|
||||
import Embed from "@/models/embed";
|
||||
import showToast from "@/utils/toast";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function EditEmbedModal({ embed, closeModal }) {
|
||||
const [error, setError] = useState(null);
|
||||
@@ -53,9 +54,7 @@ export default function EditEmbedModal({ embed, closeModal }) {
|
||||
<ChatModeSelection defaultValue={embed.chat_mode} />
|
||||
<PermittedDomains
|
||||
defaultValue={
|
||||
embed.allowlist_domains
|
||||
? JSON.parse(embed.allowlist_domains)
|
||||
: []
|
||||
safeJsonParse(embed.allowlist_domains, null) || []
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
|
||||
@@ -9,6 +9,7 @@ import { nFormatter } from "@/utils/numbers";
|
||||
import EditEmbedModal from "./EditEmbedModal";
|
||||
import CodeSnippetModal from "./CodeSnippetModal";
|
||||
import moment from "moment";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function EmbedRow({ embed }) {
|
||||
const rowRef = useRef(null);
|
||||
@@ -140,21 +141,17 @@ export default function EmbedRow({ embed }) {
|
||||
}
|
||||
|
||||
function ActiveDomains({ domainList }) {
|
||||
if (!domainList) return <p>all</p>;
|
||||
try {
|
||||
const domains = JSON.parse(domainList);
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{domains.map((domain, index) => {
|
||||
return (
|
||||
<p key={index} className="font-mono !font-normal">
|
||||
{domain}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
} catch {
|
||||
return <p>all</p>;
|
||||
}
|
||||
const domains = safeJsonParse(domainList, []);
|
||||
if (domains.length === 0) return <p>all</p>;
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{domains.map((domain, index) => {
|
||||
return (
|
||||
<p key={index} className="font-mono !font-normal">
|
||||
{domain}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import { VisibilityIcon } from "./generic";
|
||||
import { safeJsonParse } from "@/utils/request";
|
||||
|
||||
export default function AgentFlowHubCard({ item }) {
|
||||
const flow = JSON.parse(item.flow);
|
||||
const flow = safeJsonParse(item.flow, { steps: [] });
|
||||
return (
|
||||
<Link
|
||||
to={paths.communityHub.importItem(item.importId)}
|
||||
|
||||
@@ -36,6 +36,7 @@ import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";
|
||||
import CometApiLogo from "@/media/llmprovider/cometapi.png";
|
||||
import FoundryLogo from "@/media/llmprovider/foundry-local.png";
|
||||
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
|
||||
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
|
||||
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
|
||||
@@ -70,6 +71,7 @@ import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
|
||||
import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";
|
||||
import FoundryOptions from "@/components/LLMSelection/FoundryOptions";
|
||||
import GiteeAIOptions from "@/components/LLMSelection/GiteeAIOptions/index.jsx";
|
||||
import DockerModelRunnerOptions from "@/components/LLMSelection/DockerModelRunnerOptions";
|
||||
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
|
||||
@@ -160,6 +162,18 @@ export const AVAILABLE_LLM_PROVIDERS = [
|
||||
"Discover, download, and run thousands of cutting edge LLMs in a few clicks.",
|
||||
requiredConfig: ["LMStudioBasePath"],
|
||||
},
|
||||
{
|
||||
name: "Docker Model Runner",
|
||||
value: "docker-model-runner",
|
||||
logo: DockerModelRunnerLogo,
|
||||
options: (settings) => <DockerModelRunnerOptions settings={settings} />,
|
||||
description: "Run LLMs using Docker Model Runner.",
|
||||
requiredConfig: [
|
||||
"DockerModelRunnerBasePath",
|
||||
"DockerModelRunnerModelPref",
|
||||
"DockerModelRunnerModelTokenLimit",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Local AI",
|
||||
value: "localai",
|
||||
@@ -370,6 +384,7 @@ export const AVAILABLE_LLM_PROVIDERS = [
|
||||
},
|
||||
];
|
||||
|
||||
export const LLM_PREFERENCE_CHANGED_EVENT = "llm-preference-changed";
|
||||
export default function GeneralLLMPreference() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
@@ -427,6 +442,21 @@ export default function GeneralLLMPreference() {
|
||||
fetchKeys();
|
||||
}, []);
|
||||
|
||||
// Some more complex LLM options do not bubble up the change event, so we need to listen to the custom event
|
||||
// we can emit from the LLM options component using window.dispatchEvent(new Event(LLM_PREFERENCE_CHANGED_EVENT));
|
||||
useEffect(() => {
|
||||
function updateHasChanges() {
|
||||
setHasChanges(true);
|
||||
}
|
||||
window.addEventListener(LLM_PREFERENCE_CHANGED_EVENT, updateHasChanges);
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
LLM_PREFERENCE_CHANGED_EVENT,
|
||||
updateHasChanges
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = AVAILABLE_LLM_PROVIDERS.filter((llm) =>
|
||||
llm.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="artwork" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 238.96 70.87">
|
||||
<!-- Generator: Adobe Illustrator 29.8.3, SVG Export Plug-In . SVG Version: 2.1.1 Build 3) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #4285f4;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #a6a6a6;
|
||||
}
|
||||
|
||||
.st2 {
|
||||
stroke: #fff;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: .2px;
|
||||
}
|
||||
|
||||
.st2, .st3 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st4 {
|
||||
fill: #34a853;
|
||||
}
|
||||
|
||||
.st5 {
|
||||
fill: #fbbc04;
|
||||
}
|
||||
|
||||
.st6 {
|
||||
fill: #ea4335;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect x="-.11" width="239.17" height="70.87" rx="8.86" ry="8.86"/>
|
||||
<path class="st1" d="M230.21,1.42c4.1,0,7.44,3.34,7.44,7.44v53.15c0,4.1-3.34,7.44-7.44,7.44H8.75c-4.1,0-7.44-3.34-7.44-7.44V8.86C1.31,4.76,4.65,1.42,8.75,1.42h221.46M230.21,0H8.75C3.88,0-.11,3.99-.11,8.86v53.15c0,4.87,3.99,8.86,8.86,8.86h221.46c4.87,0,8.86-3.99,8.86-8.86V8.86c0-4.87-3.99-8.86-8.86-8.86h0Z"/>
|
||||
<g>
|
||||
<path class="st2" d="M83.9,18.15c0,1.48-.44,2.67-1.32,3.55-1,1.05-2.3,1.57-3.9,1.57s-2.84-.53-3.91-1.59c-1.07-1.06-1.61-2.38-1.61-3.96s.54-2.89,1.61-3.96c1.07-1.06,2.38-1.6,3.91-1.6.76,0,1.49.15,2.18.44.69.3,1.25.69,1.66,1.19l-.93.94c-.7-.84-1.67-1.26-2.91-1.26-1.12,0-2.09.39-2.9,1.18s-1.22,1.81-1.22,3.06.41,2.28,1.22,3.06c.82.79,1.78,1.18,2.9,1.18,1.19,0,2.18-.4,2.97-1.19.51-.52.81-1.23.89-2.15h-3.86v-1.28h5.15c.05.28.07.54.07.8Z"/>
|
||||
<path class="st2" d="M92.07,13.71h-4.84v3.37h4.37v1.28h-4.37v3.37h4.84v1.31h-6.21v-10.63h6.21v1.31h0Z"/>
|
||||
<path class="st2" d="M97.83,23.03h-1.37v-9.32h-2.97v-1.31h7.3v1.31h-2.97s0,9.32,0,9.32Z"/>
|
||||
<path class="st2" d="M106.08,23.03v-10.63h1.36v10.63h-1.36Z"/>
|
||||
<path class="st2" d="M113.51,23.03h-1.37v-9.32h-2.97v-1.31h7.3v1.31h-2.97s0,9.32,0,9.32Z"/>
|
||||
<path class="st2" d="M130.3,21.66c-1.05,1.08-2.34,1.61-3.9,1.61s-2.85-.54-3.9-1.61c-1.05-1.07-1.57-2.39-1.57-3.94s.52-2.87,1.57-3.94c1.04-1.08,2.34-1.61,3.9-1.61s2.84.54,3.89,1.62c1.05,1.08,1.57,2.39,1.57,3.93s-.52,2.87-1.57,3.94ZM123.52,20.77c.79.8,1.75,1.19,2.89,1.19s2.1-.4,2.89-1.19c.79-.8,1.18-1.81,1.18-3.05s-.4-2.26-1.18-3.05c-.78-.8-1.75-1.19-2.89-1.19s-2.1.4-2.89,1.19c-.78.8-1.18,1.81-1.18,3.05s.4,2.26,1.18,3.05Z"/>
|
||||
<path class="st2" d="M133.79,23.03v-10.63h1.66l5.17,8.27h.06l-.06-2.05v-6.22h1.37v10.63h-1.43l-5.41-8.67h-.06l.06,2.05v6.62h-1.37,0Z"/>
|
||||
</g>
|
||||
<path class="st3" d="M120.61,38.54c-4.17,0-7.56,3.17-7.56,7.53s3.4,7.53,7.56,7.53,7.56-3.2,7.56-7.53-3.4-7.53-7.56-7.53ZM120.61,50.64c-2.28,0-4.25-1.88-4.25-4.57s1.97-4.57,4.25-4.57,4.25,1.85,4.25,4.57-1.97,4.57-4.25,4.57ZM104.11,38.54c-4.17,0-7.56,3.17-7.56,7.53s3.4,7.53,7.56,7.53,7.56-3.2,7.56-7.53-3.4-7.53-7.56-7.53ZM104.11,50.64c-2.28,0-4.25-1.88-4.25-4.57s1.97-4.57,4.25-4.57,4.25,1.85,4.25,4.57-1.97,4.57-4.25,4.57ZM84.48,40.85v3.2h7.65c-.23,1.8-.83,3.11-1.74,4.03-1.11,1.11-2.85,2.34-5.91,2.34-4.71,0-8.39-3.8-8.39-8.51s3.68-8.51,8.39-8.51c2.54,0,4.4,1,5.76,2.28l2.26-2.26c-1.91-1.83-4.45-3.22-8.02-3.22-6.45,0-11.87,5.25-11.87,11.7s5.42,11.7,11.87,11.7c3.48,0,6.11-1.14,8.16-3.28,2.11-2.11,2.77-5.08,2.77-7.48,0-.74-.06-1.43-.17-2h-10.76,0ZM164.75,43.33c-.63-1.68-2.54-4.8-6.45-4.8s-7.11,3.05-7.11,7.53c0,4.22,3.2,7.53,7.48,7.53,3.45,0,5.45-2.11,6.28-3.34l-2.57-1.71c-.86,1.26-2.03,2.08-3.71,2.08s-2.88-.77-3.65-2.28l10.08-4.17-.34-.86h0ZM154.47,45.84c-.09-2.91,2.26-4.4,3.94-4.4,1.31,0,2.43.66,2.8,1.6l-6.74,2.8ZM146.28,53.15h3.31v-22.15h-3.31v22.15ZM140.86,40.22h-.11c-.74-.88-2.17-1.68-3.97-1.68-3.77,0-7.22,3.31-7.22,7.56s3.45,7.51,7.22,7.51c1.8,0,3.22-.8,3.97-1.71h.11v1.08c0,2.88-1.54,4.42-4.03,4.42-2.03,0-3.28-1.46-3.8-2.68l-2.88,1.2c.83,2,3.03,4.45,6.68,4.45,3.88,0,7.16-2.28,7.16-7.85v-13.53h-3.14v1.23ZM137.06,50.64c-2.28,0-4.2-1.91-4.2-4.54s1.91-4.6,4.2-4.6,4.03,1.94,4.03,4.6-1.77,4.54-4.03,4.54ZM180.26,31h-7.92v22.15h3.31v-8.39h4.62c3.66,0,7.27-2.65,7.27-6.88s-3.6-6.88-7.27-6.88ZM180.34,41.68h-4.7v-7.59h4.7c2.47,0,3.87,2.05,3.87,3.8s-1.4,3.8-3.87,3.8h0ZM200.77,38.5c-2.39,0-4.87,1.05-5.9,3.39l2.94,1.23c.63-1.23,1.79-1.62,3.02-1.62,1.71,0,3.45,1.03,3.48,2.85v.23c-.6-.34-1.88-.86-3.45-.86-3.16,0-6.38,1.74-6.38,4.99,0,2.96,2.59,4.87,5.5,4.87,2.22,0,3.45-1,4.22-2.16h.11v1.71h3.19v-8.49c0-3.93-2.94-6.13-6.73-6.13h0ZM200.38,50.63c-1.08,0-2.59-.54-2.59-1.88,0-1.71,1.88-2.37,3.5-2.37,1.45,0,2.14.31,3.02.74-.26,2.05-2.02,3.5-3.93,3.5ZM219.12,38.98l-3.79,9.6h-.11l-3.93-9.6h-3.56l5.9,13.42-3.36,7.47h3.45l9.09-20.89h-3.68ZM189.35,53.15h3.31v-22.15h-3.31v22.15Z"/>
|
||||
<g>
|
||||
<path class="st6" d="M36.6,34.41l-18.86,20.02s0,0,0,.01c.58,2.17,2.56,3.77,4.92,3.77.94,0,1.83-.26,2.58-.7l.06-.04,21.23-12.25-9.94-10.82Z"/>
|
||||
<path class="st5" d="M55.68,31h-.02s-9.17-5.33-9.17-5.33l-10.33,9.19,10.36,10.36,9.12-5.26c1.6-.86,2.68-2.55,2.68-4.49s-1.07-3.61-2.65-4.47h0Z"/>
|
||||
<path class="st0" d="M17.73,16.44c-.11.42-.17.86-.17,1.31v35.38c0,.45.06.89.17,1.31l19.51-19.51-19.51-18.49Z"/>
|
||||
<path class="st4" d="M36.74,35.43l9.76-9.76-21.21-12.3c-.77-.46-1.67-.73-2.63-.73-2.36,0-4.34,1.6-4.92,3.78h0s19,19,19,19h0Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -8,6 +8,7 @@ import MobileConnection from "@/models/mobile";
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import Logo from "@/media/logo/anything-llm-infinity.png";
|
||||
import paths from "@/utils/paths";
|
||||
import GetOnGooglePlay from "./gplay-badge.svg";
|
||||
|
||||
export default function MobileConnectModal({ isOpen, onClose }) {
|
||||
return (
|
||||
@@ -37,13 +38,23 @@ export default function MobileConnectModal({ isOpen, onClose }) {
|
||||
Go mobile. Stay local. AnythingLLM Mobile.
|
||||
</p>
|
||||
<p className="text-[#FFF] text-lg">
|
||||
AnythingLLM for mobile allows you to connect or clone your
|
||||
workspace's chats, threads and documents for you to use on the go.
|
||||
AnythingLLM for mobile allows you to connect to your workspace's
|
||||
chats, threads, tools, and documents for you to use on the go.
|
||||
<br />
|
||||
<br />
|
||||
Run with local models on your phone privately or relay chats
|
||||
directly to this instance seamlessly.
|
||||
</p>
|
||||
<Link
|
||||
to="https://play.google.com/store/apps/details?id=com.anythingllm"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src={GetOnGooglePlay}
|
||||
alt="Get on Google Play"
|
||||
className="w-[150px] h-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* right column */}
|
||||
|
||||
@@ -4,13 +4,8 @@ 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,
|
||||
FALLBACKS,
|
||||
} from "@/pages/OnboardingFlow/Steps/DataHandling";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ProviderPrivacy from "@/components/ProviderPrivacy";
|
||||
|
||||
export default function PrivacyAndDataHandling() {
|
||||
const [settings, setSettings] = useState({});
|
||||
@@ -51,8 +46,8 @@ export default function PrivacyAndDataHandling() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<ThirdParty settings={settings} />
|
||||
<div className="overflow-x-auto flex flex-col gap-y-6 pt-6">
|
||||
<ProviderPrivacy />
|
||||
<TelemetryLogs settings={settings} />
|
||||
</div>
|
||||
)}
|
||||
@@ -62,88 +57,6 @@ export default function PrivacyAndDataHandling() {
|
||||
);
|
||||
}
|
||||
|
||||
function ThirdParty({ settings }) {
|
||||
const llmChoice = settings?.LLMProvider || "openai";
|
||||
const embeddingEngine = settings?.EmbeddingEngine || "openai";
|
||||
const vectorDb = settings?.VectorDB || "lancedb";
|
||||
const { t } = useTranslation();
|
||||
|
||||
const LLMSelection =
|
||||
LLM_SELECTION_PRIVACY?.[llmChoice] || FALLBACKS.LLM(llmChoice);
|
||||
const EmbeddingEngine =
|
||||
EMBEDDING_ENGINE_PRIVACY?.[embeddingEngine] ||
|
||||
FALLBACKS.EMBEDDING(embeddingEngine);
|
||||
const VectorDb = VECTOR_DB_PRIVACY?.[vectorDb] || FALLBACKS.VECTOR(vectorDb);
|
||||
|
||||
return (
|
||||
<div className="py-8 w-full flex items-start justify-center flex-col gap-y-6 border-b-2 border-theme-sidebar-border">
|
||||
<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-theme-text-primary text-base font-bold">
|
||||
{t("privacy.llm")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={LLMSelection.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{LLMSelection.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{LLMSelection.description.map((desc) => (
|
||||
<li className="text-theme-text-secondary 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-theme-text-primary text-base font-bold">
|
||||
{t("privacy.embedding")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={EmbeddingEngine.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{EmbeddingEngine.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{EmbeddingEngine.description.map((desc) => (
|
||||
<li className="text-theme-text-secondary text-sm">{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-2 pb-4">
|
||||
<div className="text-theme-text-primary text-base font-bold">
|
||||
{t("privacy.vector")}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={VectorDb.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{VectorDb.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{VectorDb.description.map((desc) => (
|
||||
<li className="text-theme-text-secondary text-sm">{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TelemetryLogs({ settings }) {
|
||||
const [telemetry, setTelemetry] = useState(
|
||||
settings?.DisableTelemetry !== "true"
|
||||
@@ -164,7 +77,6 @@ function TelemetryLogs({ settings }) {
|
||||
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="">
|
||||
|
||||
@@ -17,7 +17,7 @@ export function ChecklistItem({ id, title, action, onAction, icon: Icon }) {
|
||||
const shouldComplete = await onAction();
|
||||
if (shouldComplete) {
|
||||
const stored = window.localStorage.getItem(CHECKLIST_STORAGE_KEY);
|
||||
const completedItems = stored ? JSON.parse(stored) : {};
|
||||
const completedItems = safeJsonParse(stored, {});
|
||||
completedItems[id] = true;
|
||||
window.localStorage.setItem(
|
||||
CHECKLIST_STORAGE_KEY,
|
||||
|
||||
@@ -71,7 +71,6 @@ export default function ExploreFeatures() {
|
||||
)}
|
||||
onPrimaryAction={chatWithAgent}
|
||||
onSecondaryAction={buildAgentFlow}
|
||||
isNew={true}
|
||||
/>
|
||||
<FeatureCard
|
||||
title={t("main-page.exploreMore.features.slashCommands.title")}
|
||||
@@ -101,7 +100,6 @@ export default function ExploreFeatures() {
|
||||
)}
|
||||
onPrimaryAction={setSystemPrompt}
|
||||
onSecondaryAction={managePromptVariables}
|
||||
isNew={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import illustration from "@/media/illustrations/create-workspace.png";
|
||||
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";
|
||||
|
||||
export default function CreateWorkspace({
|
||||
setHeader,
|
||||
setForwardBtn,
|
||||
setBackBtn,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const createWorkspaceRef = useRef();
|
||||
const TITLE = t("onboarding.workspace.title");
|
||||
const DESCRIPTION = t("onboarding.workspace.description");
|
||||
|
||||
useEffect(() => {
|
||||
setHeader({ title: TITLE, description: DESCRIPTION });
|
||||
setBackBtn({ showing: false, disabled: false, onClick: handleBack });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceName.length > 0) {
|
||||
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
|
||||
} else {
|
||||
setForwardBtn({ showing: true, disabled: true, onClick: handleForward });
|
||||
}
|
||||
}, [workspaceName]);
|
||||
|
||||
const handleCreate = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = new FormData(e.target);
|
||||
const { workspace, error } = await Workspace.new({
|
||||
name: form.get("name"),
|
||||
onboardingComplete: true,
|
||||
});
|
||||
if (!!workspace) {
|
||||
showToast(
|
||||
"Workspace created successfully! Taking you to home...",
|
||||
"success"
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
navigate(paths.home());
|
||||
} else {
|
||||
showToast(`Failed to create workspace: ${error}`, "error");
|
||||
}
|
||||
};
|
||||
|
||||
function handleForward() {
|
||||
createWorkspaceRef.current.click();
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
navigate(paths.onboarding.survey());
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleCreate}
|
||||
className="w-full flex items-center justify-center flex-col gap-y-2"
|
||||
>
|
||||
<img src={illustration} alt="Create workspace" />
|
||||
<div className="flex flex-col gap-y-4 w-full max-w-[600px]">
|
||||
{" "}
|
||||
<div className="w-full mt-4">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block mb-3 text-sm font-medium text-white"
|
||||
>
|
||||
{t("common.workspaces-name")}
|
||||
</label>
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
className="border-none bg-theme-settings-input-bg text-white focus:outline-primary-button active:outline-primary-button placeholder:text-theme-settings-input-placeholder outline-none text-sm rounded-lg block w-full p-2.5"
|
||||
placeholder="My Workspace"
|
||||
required={true}
|
||||
autoComplete="off"
|
||||
onChange={(e) => setWorkspaceName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
ref={createWorkspaceRef}
|
||||
hidden
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,499 +1,11 @@
|
||||
import PreLoader from "@/components/Preloader";
|
||||
import System from "@/models/system";
|
||||
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
|
||||
import OpenAiLogo from "@/media/llmprovider/openai.png";
|
||||
import GenericOpenAiLogo from "@/media/llmprovider/generic-openai.png";
|
||||
import AzureOpenAiLogo from "@/media/llmprovider/azure.png";
|
||||
import AnthropicLogo from "@/media/llmprovider/anthropic.png";
|
||||
import GeminiLogo from "@/media/llmprovider/gemini.png";
|
||||
import OllamaLogo from "@/media/llmprovider/ollama.png";
|
||||
import TogetherAILogo from "@/media/llmprovider/togetherai.png";
|
||||
import FireworksAILogo from "@/media/llmprovider/fireworksai.jpeg";
|
||||
import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
|
||||
import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
|
||||
import LocalAiLogo from "@/media/llmprovider/localai.png";
|
||||
import MistralLogo from "@/media/llmprovider/mistral.jpeg";
|
||||
import HuggingFaceLogo from "@/media/llmprovider/huggingface.png";
|
||||
import PerplexityLogo from "@/media/llmprovider/perplexity.png";
|
||||
import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg";
|
||||
import NovitaLogo from "@/media/llmprovider/novita.png";
|
||||
import GroqLogo from "@/media/llmprovider/groq.png";
|
||||
import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
|
||||
import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
|
||||
import LiteLLMLogo from "@/media/llmprovider/litellm.png";
|
||||
import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
|
||||
import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
|
||||
import APIPieLogo from "@/media/llmprovider/apipie.png";
|
||||
import XAILogo from "@/media/llmprovider/xai.png";
|
||||
import ZAiLogo from "@/media/llmprovider/zai.png";
|
||||
import CohereLogo from "@/media/llmprovider/cohere.png";
|
||||
import ZillizLogo from "@/media/vectordbs/zilliz.png";
|
||||
import AstraDBLogo from "@/media/vectordbs/astraDB.png";
|
||||
import ChromaLogo from "@/media/vectordbs/chroma.png";
|
||||
import PineconeLogo from "@/media/vectordbs/pinecone.png";
|
||||
import LanceDbLogo from "@/media/vectordbs/lancedb.png";
|
||||
import WeaviateLogo from "@/media/vectordbs/weaviate.png";
|
||||
import QDrantLogo from "@/media/vectordbs/qdrant.png";
|
||||
import MilvusLogo from "@/media/vectordbs/milvus.png";
|
||||
import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
|
||||
import PPIOLogo from "@/media/llmprovider/ppio.png";
|
||||
import PGVectorLogo from "@/media/vectordbs/pgvector.png";
|
||||
import DPAISLogo from "@/media/llmprovider/dpais.png";
|
||||
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";
|
||||
import CometApiLogo from "@/media/llmprovider/cometapi.png";
|
||||
import FoundryLogo from "@/media/llmprovider/foundry-local.png";
|
||||
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import paths from "@/utils/paths";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const LLM_SELECTION_PRIVACY = {
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to OpenAI",
|
||||
],
|
||||
logo: OpenAiLogo,
|
||||
},
|
||||
azure: {
|
||||
name: "Azure OpenAI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your text and embedding text are not visible to OpenAI or Microsoft",
|
||||
],
|
||||
logo: AzureOpenAiLogo,
|
||||
},
|
||||
anthropic: {
|
||||
name: "Anthropic",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to Anthropic",
|
||||
],
|
||||
logo: AnthropicLogo,
|
||||
},
|
||||
gemini: {
|
||||
name: "Google Gemini",
|
||||
description: [
|
||||
"Your chats are de-identified and used in training",
|
||||
"Your prompts and document text used in response creation are visible to Google",
|
||||
],
|
||||
logo: GeminiLogo,
|
||||
},
|
||||
"nvidia-nim": {
|
||||
name: "NVIDIA NIM",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running the NVIDIA NIM",
|
||||
],
|
||||
logo: NvidiaNimLogo,
|
||||
},
|
||||
lmstudio: {
|
||||
name: "LMStudio",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LMStudio",
|
||||
],
|
||||
logo: LMStudioLogo,
|
||||
},
|
||||
localai: {
|
||||
name: "LocalAI",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LocalAI",
|
||||
],
|
||||
logo: LocalAiLogo,
|
||||
},
|
||||
ollama: {
|
||||
name: "Ollama",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running Ollama models",
|
||||
],
|
||||
logo: OllamaLogo,
|
||||
},
|
||||
togetherai: {
|
||||
name: "TogetherAI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to TogetherAI",
|
||||
],
|
||||
logo: TogetherAILogo,
|
||||
},
|
||||
fireworksai: {
|
||||
name: "FireworksAI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to Fireworks AI",
|
||||
],
|
||||
logo: FireworksAILogo,
|
||||
},
|
||||
mistral: {
|
||||
name: "Mistral",
|
||||
description: [
|
||||
"Your prompts and document text used in response creation are visible to Mistral",
|
||||
],
|
||||
logo: MistralLogo,
|
||||
},
|
||||
huggingface: {
|
||||
name: "HuggingFace",
|
||||
description: [
|
||||
"Your prompts and document text used in response are sent to your HuggingFace managed endpoint",
|
||||
],
|
||||
logo: HuggingFaceLogo,
|
||||
},
|
||||
perplexity: {
|
||||
name: "Perplexity AI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to Perplexity AI",
|
||||
],
|
||||
logo: PerplexityLogo,
|
||||
},
|
||||
openrouter: {
|
||||
name: "OpenRouter",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to OpenRouter",
|
||||
],
|
||||
logo: OpenRouterLogo,
|
||||
},
|
||||
novita: {
|
||||
name: "Novita AI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to Novita AI",
|
||||
],
|
||||
logo: NovitaLogo,
|
||||
},
|
||||
groq: {
|
||||
name: "Groq",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to Groq",
|
||||
],
|
||||
logo: GroqLogo,
|
||||
},
|
||||
koboldcpp: {
|
||||
name: "KoboldCPP",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running KoboldCPP",
|
||||
],
|
||||
logo: KoboldCPPLogo,
|
||||
},
|
||||
textgenwebui: {
|
||||
name: "Oobabooga Web UI",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running the Oobabooga Text Generation Web UI",
|
||||
],
|
||||
logo: TextGenWebUILogo,
|
||||
},
|
||||
"generic-openai": {
|
||||
name: "Generic OpenAI compatible service",
|
||||
description: [
|
||||
"Data is shared according to the terms of service applicable with your generic endpoint provider.",
|
||||
],
|
||||
logo: GenericOpenAiLogo,
|
||||
},
|
||||
cohere: {
|
||||
name: "Cohere",
|
||||
description: [
|
||||
"Data is shared according to the terms of service of cohere.com and your localities privacy laws.",
|
||||
],
|
||||
logo: CohereLogo,
|
||||
},
|
||||
litellm: {
|
||||
name: "LiteLLM",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the server running LiteLLM",
|
||||
],
|
||||
logo: LiteLLMLogo,
|
||||
},
|
||||
bedrock: {
|
||||
name: "AWS Bedrock",
|
||||
description: [
|
||||
"You model and chat contents are subject to the agreed EULA for AWS and the model provider on aws.amazon.com",
|
||||
],
|
||||
logo: AWSBedrockLogo,
|
||||
},
|
||||
deepseek: {
|
||||
name: "DeepSeek",
|
||||
description: ["Your model and chat contents are visible to DeepSeek"],
|
||||
logo: DeepSeekLogo,
|
||||
},
|
||||
apipie: {
|
||||
name: "APIpie.AI",
|
||||
description: [
|
||||
"Your model and chat contents are visible to APIpie in accordance with their terms of service.",
|
||||
],
|
||||
logo: APIPieLogo,
|
||||
},
|
||||
xai: {
|
||||
name: "xAI",
|
||||
description: [
|
||||
"Your model and chat contents are visible to xAI in accordance with their terms of service.",
|
||||
],
|
||||
logo: XAILogo,
|
||||
},
|
||||
zai: {
|
||||
name: "Z.AI",
|
||||
description: [
|
||||
"Your content is processed in real-time and not stored on Z.AI servers",
|
||||
"Your prompts and document text are visible to Z.AI during processing",
|
||||
"Data is processed in accordance with Z.AI's API Services terms",
|
||||
],
|
||||
logo: ZAiLogo,
|
||||
},
|
||||
ppio: {
|
||||
name: "PPIO",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to PPIO",
|
||||
],
|
||||
logo: PPIOLogo,
|
||||
},
|
||||
dpais: {
|
||||
name: "Dell Pro AI Studio",
|
||||
description: [
|
||||
"Your model and chat contents are only accessible on the computer running Dell Pro AI Studio",
|
||||
],
|
||||
logo: DPAISLogo,
|
||||
},
|
||||
moonshotai: {
|
||||
name: "Moonshot AI",
|
||||
description: [
|
||||
"Your chats may be used by Moonshot AI for training and model refinement",
|
||||
"Your prompts and document text used in response creation are visible to Moonshot AI",
|
||||
],
|
||||
logo: MoonshotAiLogo,
|
||||
},
|
||||
cometapi: {
|
||||
name: "CometAPI",
|
||||
description: [
|
||||
"Your chats will not be used for training",
|
||||
"Your prompts and document text used in response creation are visible to CometAPI",
|
||||
],
|
||||
logo: CometApiLogo,
|
||||
},
|
||||
foundry: {
|
||||
name: "Microsoft Foundry Local",
|
||||
description: [
|
||||
"Your model and chats are only accessible on the machine running Foundry Local",
|
||||
],
|
||||
logo: FoundryLogo,
|
||||
},
|
||||
giteeai: {
|
||||
name: "GiteeAI",
|
||||
description: [
|
||||
"Your model and chat contents are visible to GiteeAI in accordance with their terms of service.",
|
||||
],
|
||||
logo: GiteeAILogo,
|
||||
},
|
||||
};
|
||||
|
||||
export const VECTOR_DB_PRIVACY = {
|
||||
pgvector: {
|
||||
name: "PGVector",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your PostgreSQL instance",
|
||||
"Access to your instance is managed by you",
|
||||
],
|
||||
logo: PGVectorLogo,
|
||||
},
|
||||
chroma: {
|
||||
name: "Chroma",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Chroma instance",
|
||||
"Access to your instance is managed by you",
|
||||
],
|
||||
logo: ChromaLogo,
|
||||
},
|
||||
chromacloud: {
|
||||
name: "Chroma Cloud",
|
||||
description: [
|
||||
"Your vectors and document text are stored on Chroma's cloud service",
|
||||
"Access to your data is managed by Chroma",
|
||||
],
|
||||
logo: ChromaLogo,
|
||||
},
|
||||
pinecone: {
|
||||
name: "Pinecone",
|
||||
description: [
|
||||
"Your vectors and document text are stored on Pinecone's servers",
|
||||
"Access to your data is managed by Pinecone",
|
||||
],
|
||||
logo: PineconeLogo,
|
||||
},
|
||||
qdrant: {
|
||||
name: "Qdrant",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Qdrant instance (cloud or self-hosted)",
|
||||
],
|
||||
logo: QDrantLogo,
|
||||
},
|
||||
weaviate: {
|
||||
name: "Weaviate",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Weaviate instance (cloud or self-hosted)",
|
||||
],
|
||||
logo: WeaviateLogo,
|
||||
},
|
||||
milvus: {
|
||||
name: "Milvus",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Milvus instance (cloud or self-hosted)",
|
||||
],
|
||||
logo: MilvusLogo,
|
||||
},
|
||||
zilliz: {
|
||||
name: "Zilliz Cloud",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your Zilliz cloud cluster.",
|
||||
],
|
||||
logo: ZillizLogo,
|
||||
},
|
||||
astra: {
|
||||
name: "AstraDB",
|
||||
description: [
|
||||
"Your vectors and document text are stored on your cloud AstraDB database.",
|
||||
],
|
||||
logo: AstraDBLogo,
|
||||
},
|
||||
lancedb: {
|
||||
name: "LanceDB",
|
||||
description: [
|
||||
"Your vectors and document text are stored privately on this instance of AnythingLLM",
|
||||
],
|
||||
logo: LanceDbLogo,
|
||||
},
|
||||
};
|
||||
|
||||
export const EMBEDDING_ENGINE_PRIVACY = {
|
||||
native: {
|
||||
name: "AnythingLLM Embedder",
|
||||
description: [
|
||||
"Your document text is embedded privately on this instance of AnythingLLM",
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
},
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
description: [
|
||||
"Your document text is sent to OpenAI servers",
|
||||
"Your documents are not used for training",
|
||||
],
|
||||
logo: OpenAiLogo,
|
||||
},
|
||||
azure: {
|
||||
name: "Azure OpenAI",
|
||||
description: [
|
||||
"Your document text is sent to your Microsoft Azure service",
|
||||
"Your documents are not used for training",
|
||||
],
|
||||
logo: AzureOpenAiLogo,
|
||||
},
|
||||
localai: {
|
||||
name: "LocalAI",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running LocalAI",
|
||||
],
|
||||
logo: LocalAiLogo,
|
||||
},
|
||||
ollama: {
|
||||
name: "Ollama",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running Ollama",
|
||||
],
|
||||
logo: OllamaLogo,
|
||||
},
|
||||
lmstudio: {
|
||||
name: "LMStudio",
|
||||
description: [
|
||||
"Your document text is embedded privately on the server running LMStudio",
|
||||
],
|
||||
logo: LMStudioLogo,
|
||||
},
|
||||
openrouter: {
|
||||
name: "OpenRouter",
|
||||
description: [
|
||||
"Your document text is sent to OpenRouter's servers for processing",
|
||||
"Your document text is stored or managed according to the terms of service of OpenRouter API Terms of Service",
|
||||
],
|
||||
logo: OpenRouterLogo,
|
||||
},
|
||||
cohere: {
|
||||
name: "Cohere",
|
||||
description: [
|
||||
"Data is shared according to the terms of service of cohere.com and your localities privacy laws.",
|
||||
],
|
||||
logo: CohereLogo,
|
||||
},
|
||||
voyageai: {
|
||||
name: "Voyage AI",
|
||||
description: [
|
||||
"Data sent to Voyage AI's servers is shared according to the terms of service of voyageai.com.",
|
||||
],
|
||||
logo: VoyageAiLogo,
|
||||
},
|
||||
mistral: {
|
||||
name: "Mistral AI",
|
||||
description: [
|
||||
"Data sent to Mistral AI's servers is shared according to the terms of service of https://mistral.ai.",
|
||||
],
|
||||
logo: MistralLogo,
|
||||
},
|
||||
litellm: {
|
||||
name: "LiteLLM",
|
||||
description: [
|
||||
"Your document text is only accessible on the server running LiteLLM and to the providers you configured in LiteLLM.",
|
||||
],
|
||||
logo: LiteLLMLogo,
|
||||
},
|
||||
"generic-openai": {
|
||||
name: "Generic OpenAI compatible service",
|
||||
description: [
|
||||
"Data is shared according to the terms of service applicable with your generic endpoint provider.",
|
||||
],
|
||||
logo: GenericOpenAiLogo,
|
||||
},
|
||||
gemini: {
|
||||
name: "Google Gemini",
|
||||
description: [
|
||||
"Your document text is sent to Google Gemini's servers for processing",
|
||||
"Your document text is stored or managed according to the terms of service of Google Gemini API Terms of Service",
|
||||
],
|
||||
logo: GeminiLogo,
|
||||
},
|
||||
};
|
||||
|
||||
export const FALLBACKS = {
|
||||
LLM: (provider) => ({
|
||||
name: "Unknown",
|
||||
description: [
|
||||
`"${provider}" has no known data handling policy defined in AnythingLLM`,
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
}),
|
||||
EMBEDDING: (provider) => ({
|
||||
name: "Unknown",
|
||||
description: [
|
||||
`"${provider}" has no known data handling policy defined in AnythingLLM`,
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
}),
|
||||
VECTOR: (provider) => ({
|
||||
name: "Unknown",
|
||||
description: [
|
||||
`"${provider}" has no known data handling policy defined in AnythingLLM`,
|
||||
],
|
||||
logo: AnythingLLMIcon,
|
||||
}),
|
||||
};
|
||||
import ProviderPrivacy from "@/components/ProviderPrivacy";
|
||||
|
||||
export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
const { t } = useTranslation();
|
||||
const [llmChoice, setLLMChoice] = useState("openai");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [vectorDb, setVectorDb] = useState("pinecone");
|
||||
const [embeddingEngine, setEmbeddingEngine] = useState("openai");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const TITLE = t("onboarding.data.title");
|
||||
@@ -503,15 +15,6 @@ export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
setHeader({ title: TITLE, description: DESCRIPTION });
|
||||
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
|
||||
setBackBtn({ showing: false, disabled: false, onClick: handleBack });
|
||||
async function fetchKeys() {
|
||||
const _settings = await System.keys();
|
||||
setLLMChoice(_settings?.LLMProvider || "openai");
|
||||
setVectorDb(_settings?.VectorDB || "lancedb");
|
||||
setEmbeddingEngine(_settings?.EmbeddingEngine || "openai");
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
fetchKeys();
|
||||
}, []);
|
||||
|
||||
function handleForward() {
|
||||
@@ -522,85 +25,9 @@ export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
navigate(paths.onboarding.userSetup());
|
||||
}
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center items-center p-20">
|
||||
<PreLoader />
|
||||
</div>
|
||||
);
|
||||
|
||||
const LLMSelection =
|
||||
LLM_SELECTION_PRIVACY?.[llmChoice] || FALLBACKS.LLM(llmChoice);
|
||||
const EmbeddingEngine =
|
||||
EMBEDDING_ENGINE_PRIVACY?.[embeddingEngine] ||
|
||||
FALLBACKS.EMBEDDING(embeddingEngine);
|
||||
const VectorDb = VECTOR_DB_PRIVACY?.[vectorDb] || FALLBACKS.VECTOR(vectorDb);
|
||||
|
||||
return (
|
||||
<div className="w-full flex items-center justify-center flex-col gap-y-6">
|
||||
<div className="p-8 flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-y-2 border-b border-theme-sidebar-border pb-4">
|
||||
<div className="text-theme-text-primary text-base font-bold">
|
||||
LLM Selection
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={LLMSelection.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{LLMSelection.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{LLMSelection.description.map((desc) => (
|
||||
<li className="text-theme-text-primary text-sm">{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-2 border-b border-theme-sidebar-border pb-4">
|
||||
<div className="text-theme-text-primary text-base font-bold">
|
||||
Embedding Preference
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={EmbeddingEngine.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{EmbeddingEngine.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{EmbeddingEngine.description.map((desc) => (
|
||||
<li className="text-theme-text-primary text-sm">{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-2 pb-4">
|
||||
<div className="text-theme-text-primary text-base font-bold">
|
||||
Vector Database
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img
|
||||
src={VectorDb.logo}
|
||||
alt="LLM Logo"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<p className="text-theme-text-primary text-sm font-bold">
|
||||
{VectorDb.name}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="flex flex-col list-disc ml-4">
|
||||
{VectorDb.description.map((desc) => (
|
||||
<li className="text-theme-text-primary text-sm">{desc}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ProviderPrivacy />
|
||||
<p className="text-theme-text-secondary text-sm font-medium py-1">
|
||||
{t("onboarding.data.settingsHint")}
|
||||
</p>
|
||||
|
||||
@@ -31,6 +31,7 @@ import DellProAiStudioLogo from "@/media/llmprovider/dpais.png";
|
||||
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";
|
||||
import CometApiLogo from "@/media/llmprovider/cometapi.png";
|
||||
import GiteeAILogo from "@/media/llmprovider/giteeai.png";
|
||||
import DockerModelRunnerLogo from "@/media/llmprovider/docker-model-runner.png";
|
||||
|
||||
import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
|
||||
import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions";
|
||||
@@ -63,6 +64,7 @@ import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
|
||||
import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";
|
||||
import CometApiLLMOptions from "@/components/LLMSelection/CometApiLLMOptions";
|
||||
import GiteeAiOptions from "@/components/LLMSelection/GiteeAIOptions";
|
||||
import DockerModelRunnerOptions from "@/components/LLMSelection/DockerModelRunnerOptions";
|
||||
|
||||
import LLMItem from "@/components/LLMSelection/LLMItem";
|
||||
import System from "@/models/system";
|
||||
@@ -139,6 +141,13 @@ const LLMS = [
|
||||
description:
|
||||
"Discover, download, and run thousands of cutting edge LLMs in a few clicks.",
|
||||
},
|
||||
{
|
||||
name: "Docker Model Runner",
|
||||
value: "docker-model-runner",
|
||||
logo: DockerModelRunnerLogo,
|
||||
options: (settings) => <DockerModelRunnerOptions settings={settings} />,
|
||||
description: "Run LLMs using Docker Model Runner.",
|
||||
},
|
||||
{
|
||||
name: "Local AI",
|
||||
value: "localai",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CheckCircle } from "@phosphor-icons/react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Workspace from "@/models/workspace";
|
||||
|
||||
async function sendQuestionnaire({ email, useCase, comment }) {
|
||||
if (import.meta.env.DEV) {
|
||||
@@ -53,7 +54,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
|
||||
function handleForward() {
|
||||
if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
|
||||
navigate(paths.onboarding.createWorkspace());
|
||||
navigate(paths.home());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
}
|
||||
|
||||
function skipSurvey() {
|
||||
navigate(paths.onboarding.createWorkspace());
|
||||
navigate(paths.home());
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
@@ -91,6 +92,19 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
setBackBtn({ showing: true, disabled: false, onClick: handleBack });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function createDefaultWorkspace() {
|
||||
const workspaces = await Workspace.all();
|
||||
if (workspaces.length === 0) {
|
||||
await Workspace.new({
|
||||
name: t("new-workspace.placeholder"),
|
||||
onboardingComplete: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
createDefaultWorkspace();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
@@ -102,7 +116,7 @@ export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
|
||||
comment: formData.get("comment") || null,
|
||||
});
|
||||
|
||||
navigate(paths.onboarding.createWorkspace());
|
||||
navigate(paths.home());
|
||||
};
|
||||
|
||||
if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import LLMPreference from "./LLMPreference";
|
||||
import UserSetup from "./UserSetup";
|
||||
import DataHandling from "./DataHandling";
|
||||
import Survey from "./Survey";
|
||||
import CreateWorkspace from "./CreateWorkspace";
|
||||
|
||||
const OnboardingSteps = {
|
||||
home: Home,
|
||||
@@ -14,7 +13,6 @@ const OnboardingSteps = {
|
||||
"user-setup": UserSetup,
|
||||
"data-handling": DataHandling,
|
||||
survey: Survey,
|
||||
"create-workspace": CreateWorkspace,
|
||||
};
|
||||
|
||||
export default OnboardingSteps;
|
||||
|
||||
@@ -36,8 +36,9 @@ const ENABLED_PROVIDERS = [
|
||||
"foundry",
|
||||
"zai",
|
||||
"giteeai",
|
||||
"cohere",
|
||||
"docker-model-runner",
|
||||
// TODO: More agent support.
|
||||
// "cohere", // Has tool calling and will need to build explicit support
|
||||
// "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested.
|
||||
];
|
||||
const WARN_PERFORMANCE = [
|
||||
@@ -46,6 +47,7 @@ const WARN_PERFORMANCE = [
|
||||
"ollama",
|
||||
"localai",
|
||||
"textgenwebui",
|
||||
"docker-model-runner",
|
||||
];
|
||||
|
||||
const LLM_DEFAULT = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user