mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
* fix: typo in contribution guidelines, update project metadata and pull_request_temp...md (#5010) Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * bump copyright year resolves #5017 * feat: update light mode UI sidebar (#4996) * implement light mode sidebar redesign * Abstract hardcoded hex values into reusable css variables * reorder ternary and apply bold font on hovered workspaces * Remove double icon hack and use a state tracking whether workspace item is being hovered over for fill styles * lint * convert css variables and custom classes to default tailwind classes * remove grab icon filling on hover logic * revert css vars to original values * remove light mode css vars | change bg of sidebar in light mode to right color | make icons correct color in light mode * revert dark mode change --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix(frontend): fix event listener memory leak in useIsDisabled hook (#5027) fix: optimize event listener management in useIsDisabled hook * feat: dedicated dark theme option with system preference support (#5007) * implement OS level theme switching and dark mode option * simplify * fix logo bug in login | place back useTheme comment --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix cleanup pr workflow * Implement new home page redesign (#4931) * remove legacy home page components, update home page to new layout * update PromptInput component styles to match new designs, make quick action buttons functional * home page chat creates new thread in last used workspace * fix slash commands and agent popup on home page * disable llm workspace selector action in home page * add drag and drop file support to home page * fix behavior of drag and drop on home page * handle pasting attachments in home page * update empty state of workspace chat to use new ui * update empty workspace ui to match home page design, fix flickering loading states * convert quick action buttons to component, add to empty state ws chat * fix hover state light mode in quick actions * add suggested messages subcomponent to empty ws/thread * adjust width, rounded edges of prompt input * only show quick actions for admin/manager role * fix hover states for quick actions and suggested messages component * make upload document quick action trigger parsed document upload * fix mic behavior in homepage, ws chat, ws thread chat * fix margin between prompt input and quick actions * Simplify message presets by removing heading input (#4915) * Remove heading input from message presets, merge legacy headings on edit * filter out empty messages from state after saving * mark form as dirty on input change * styling --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * convert SuggestedMessages to component, render SuggestedMessages in home page to target ws * fix broken handleMessageChange reference * add translations for QuickActions * lint * fix home page chat submission broken by PromptInput onChange removal * fix prompt input remount race condition, home page suggested message flicker * remove unused handleSendSuggestedMessage from ChatHistory * add greeting text to main-page translations, remove defaults * fix file deletion in parsed files menu on home page * add virtual thread sidebar state and workspace indicator on home page * show workspace llm selector on home page when workspace exists * show home page for all user roles with rbac quick actions * fix positioning of agent and slash command popups * remove workspace indicator from home page, match empty state spacing * Normalize translations for home page redesign (#4986) * normalize translations * update translations with DMR * accidentally changed es translation * normalize translations for main-page.greeting * update translations with DMR --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * update translations * create new workspace in native language Cleanup workspace page from empty state handling * update quick action show logic * fix send button --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: GitLab connector infinite loop and rate limit crash for large repos (#5021) * Fix infinite loop and rate limit crashes * simplify logic | add max-retries to fetchNextPage and fetchSingleFileContents --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: add password character validation to onboarding single-user setup (#5037) * fix single user mode password bug * share const --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Native Tool calling (#5071) * checkpoint * test MCP and flows * add native tool call detection back to LMStudio * add native tool call loops for Ollama * Add ablity detection to DMR (regex parse) * bedrock and generic openai with ENV flag * deepseek native tool calling * localAI native function * groq support * linting, add litellm and OR native tool calling via flag * fix: resolve Gemini agent 400 error on tool call responses (#5054) * add gtc__ prefix to tool call names in Gemini agent message formatting * resolve Gemini agent 400 error on tool call responses * add comments explaining geminis thought signatures --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: prevent CMD/CTRL+Arrow scroll from overriding textarea cursor movement (#5053) prevent CMD/CTRL+Arrow scroll from overriding textarea cursor movement Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * lint * Normalize scraper runtimeargs for bulk-scraper (#5083) resolves #5078 closes #5079 * resolve Ollama string strict num_ctx resolves #5081 * Lemonade integration (#5077) * lemonade integration * lemonade embedder * log * load model * readme updates * update embedder privacy entry * fix max tool call stack abort flow * v1.11.1 Release tags (#5107) bump tag * 5112 or stream metrics and finish reason (#5117) * update metric tracking for OR + fix finish_reason missing from transitive chunks * linting + comments closes #5113 resolves #5112 * Fix bug where `yarn setup:envs` fails if any .env file already exists. (#5116) Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: show actionable error when LMStudio model listing fails or returns empty (#5131) * fix: show actionable error when LMStudio model listing fails or returns empty When the model listing request completes but returns no models (due to connection failure, wrong URL, or server unreachable), the dropdown now shows "No models found — check LMStudio is running and accessible" instead of "--loading available models--", making it possible to distinguish a failed request from one still in progress. Affects both LLM and embedding provider selection components. Closes recurring UX confusion reported in #3519, #1338, #3656. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * UI warning tooltip --------- Co-authored-by: Morgan Giddings <morgan@MG-Mac-Studio.home.arpa> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Add automatic chat mode with native tool calling support (#5140) Introduces a new automatic chat mode (now the default) that automatically invokes tools when the provider supports native tool calling. Conditionally shows/hides the @agent command based on whether native tooling is available. - Add supportsNativeToolCalling() to AI providers (OpenAI, Anthropic, Azure always support; others opt-in via ENV) - Update all locale translations with new mode descriptions - Enhance translator to preserve Trans component tags - Remove deprecated ability tags UI * Revert "Add automatic chat mode with native tool calling support (#5140)" - Need to support documents in agents - Need to support images in agent mode This reverts commit4c69960dca. * improve translation script * patch attempt for GH cleanup tag * workflow -wip * fix type * split cleanup * vague GH worker error - try to resolve via repo-name * Test dispatch workflow * Remove test workflow * native tool calling detection for novita * fix sidebar and add translations to sidebar * add translations * Sidebar updates (#5154) * fix sidebar and add translations to sidebar * add translations * Debug cleanup workflow * Debug cleanup workflow * Debug cleanup workflow * Use ALLM_RW_PACKAGES for package cleanup * Remove Google web-search Programmable SERP (#5156) * refactor: refactor agent skills settings page to use i18n translation keys (#5146) * refactor agent skills to read from translation keys instead of hardcoded strings * add missing sql agent description key * Remove fallbacks * adjust translation * swap to factor pattern * normalize translations (#5147) * normalize translations * run translator job * translations --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * chore: add ESLint to `/collector` (#5128) * add eslint config to /collector * prettier formatting * fix unused * fix undefined * disable lines * lockfile --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * chore: add ESLint to `/server` (#5126) * add eslint config to server * add break statements to switch case * add support for browser globals and turn off empty catch blocks * disable lines with useless try/catch wrappers * format * fix no-undef errors * disbale lines violating no-unsafe-finally * ignore syncStaticLists.mjs * use proper null check for creatorId instead of unreachable nullish coalescing * remove unneeded typescript eslint comment * make no-unused-private-class-members a warning * disable line for no-empty-objects * add new lint script * fix no-unused-vars violations * make no-unsued-vars an error --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Fix: Azure OpenAI model key collision (#5092) * fix: Migrate AzureOpenAI model key from OPEN_MODEL_PREF to prevent the naming collision. No effort necessary from current users. * test: add backwards compat tests for AzureOpenAI model key migration * patch missing env example file * linting --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * feat: Add tooltip for paperclip attach button when no files are parsed (#5139) * fix broken tooltip * fix tooltip not showing on homepage * fix tooltip rendering behind input on homepage --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> * fix: add missing /wiki to Confluence cloud citation URLs (#5167) fix: add /wiki to Confluence cloud page URLs in citations * Strip thinking from copy message outputs (#5179) * linting & show descriptive error for bad `addtoWorkspace` request body resolves #5172 * Add custom fetch to embedder for Ollama (#5180) Refactor ollama timeout to be shared. Add custom fetch to embedder for ollama as well * chore: add script to detect and prune unused translation keys (#5141) * add script to prune dead translation keys * add support for dynamic translation keys * improve performance of script * fix dynamic t() detection and add keyboard shortcut keys to allowlist * rename scripts * change commands --------- Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * chore: add ESLint CI workflow (#5160) add lint CI GitHub Action * patch plural keys * add ToS for brevity * Remove `use_mlock` from Ollama to solve `WARN` logs in ollama 0.17 resolves #5182 * Implement v2 chat layout designs (#5074) * New chat history layout with chat bubbles (#4985) * new chat history layout, remove message alignment setting * remove orphaned chat alignment hook and MessageDirection * remove workspace profile picture setting and fetch * clean up unnecessary changes * add light mode colors to chat ui and main page backgrounds * update chat message and action icon colors for light mode * update thinking and agent ui, layout, sizing * update user message uploaded images ui * update thought, agent containers to use new colors * add truncatable content with gradient to user chat messages * fix citations margin * implement new edit message UI with save and submit actions * add translations for TruncatableContent subcomponent * remove unused props * fix text colors for default mode chats, agent, thoughts container * Normalize translations for new chat history layout (#5022) * normalize translations * update translations with DMR * lint * fix mismatched home container colors * fix: add password character validation to onboarding single-user setup (#5037) * fix single user mode password bug * share const --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Native Tool calling (#5071) * checkpoint * test MCP and flows * add native tool call detection back to LMStudio * add native tool call loops for Ollama * Add ablity detection to DMR (regex parse) * bedrock and generic openai with ENV flag * deepseek native tool calling * localAI native function * groq support * linting, add litellm and OR native tool calling via flag * fix: resolve Gemini agent 400 error on tool call responses (#5054) * add gtc__ prefix to tool call names in Gemini agent message formatting * resolve Gemini agent 400 error on tool call responses * add comments explaining geminis thought signatures --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: prevent CMD/CTRL+Arrow scroll from overriding textarea cursor movement (#5053) prevent CMD/CTRL+Arrow scroll from overriding textarea cursor movement Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * linting, assistant speaker spacing and order, copy/edit order --------- Co-authored-by: Marcello Fitton <106866560+angelplusultra@users.noreply.github.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Implement new citations UI (#5038) * new chat history layout, remove message alignment setting * remove orphaned chat alignment hook and MessageDirection * remove workspace profile picture setting and fetch * clean up unnecessary changes * add light mode colors to chat ui and main page backgrounds * update chat message and action icon colors for light mode * update thinking and agent ui, layout, sizing * update user message uploaded images ui * update thought, agent containers to use new colors * add truncatable content with gradient to user chat messages * fix citations margin * implement new edit message UI with save and submit actions * add translations for TruncatableContent subcomponent * remove unused props * fix text colors for default mode chats, agent, thoughts container * Normalize translations for new chat history layout (#5022) * normalize translations * update translations with DMR * lint * fix mismatched home container colors * implement new citations ui with sources sidebar * bottom sheet for mobile citations * convert mobile citations bottom sheet to new modal design * add score, border separators for mobile citations modal * push down sources sidebar in password/multiuser mode * fix animation gap, simplify sources sidebar by splitting state to persist data on animation * add english translations * fix spacing from citations sidebar when user has auth * Normalize translations for new citation UI (#5087) * normalize translations * update translations using DMR * fix pluralize to use i18n native solution change reset to immediate clear fix spacing for TTS when showing or not to not have space * proper pluralize * hide metrics on mobile, fix last message padding on mobile --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * New prompt input ui/tools menu (#5070) * wip new prompt input ui/tools menu * fix colors for prompt input * redesign workspace llm selector, extract text size + model picker to components * refactor ToolsMenu component * fix colors/refactor WorkspaceModelPicker * fix spacing in ws model picker, change order of tools menu tabs * fix slash commands showing /reset instead of /exit during active agent session * refactor ToolsMenu to be much simpler * cleanup, fix behavior of setupup provider in WorkspaceModelPicker * simplify AgentSkillsTab toggle logic * add english translations for new components * remove legacy slash command/agent popups, add ToolsMenu keyboard nav * fix spacing of workspace model picker text * fix SourcesSidebar and TextSizeMenu positioning after merge * fix keyboard nav in ToolsMenu when clicking on tools button to open * typo * only auto pop up tools menu when prompt input is empty with / * fix z index for tools menu on citation * fix behavior of / in prompt input * move global window agent session state to module level variable * fix prompt input not clearing on /reset * missing translations * revert translating slash command * fix STT auto-submit not working on home page * Normalize translations for new prompt input/tools menu UI (#5130) * normalize translations * update translations using DMR script * normalize translations * update translations using DMR script * remove slash_exit * fix skills.js import after merge * fix tooltip z-index rendering behind citations * patch translation prune script to not remove special cases * updates to tools input * factory translations * use safeJsonParse in clearPromptInputDraft * normalize translations * disable agent skill toggles during active agent sessions + show tooltip on disabled * normalize translations * handle enter key behavior when tools menu is open * fix unfocusable modal for slash command edit/new * fix sending prompt when editing/creating slash commands * hide/show agent skills in tools menu based on role * container borders for dark/light mode compliance to designs --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * update how tooltip works for agent menu * update prompt input to show agent button with CTA in agent panel for user clarify update agent session start prompt button in input * translations * translations + move regex for slash commands to constants * fix open sidebar ux * fix tools menu to always open to slash commands, dismiss auto pop up * fix sidebar open/close button overlapping with ws model picker --------- Co-authored-by: Sean Hatfield <seanhatfield5@gmail.com> Co-authored-by: Marcello Fitton <106866560+angelplusultra@users.noreply.github.com> * patch slashcommand popup to be usePortal * Improve zh_TW Traditional Chinese locale * Improve zh_TW Traditional Chinese locale (#5187) * lint * fix schema not persisting in DB connector * Improve build times for tests and lint (#5193) * test build skip * reset file * Support Agent stream metric reporting (#5197) * Report citations for Agent call stacks (#5199) * sanitize promptReply Output * Add FileRow Indentation on Documents Picker (#5201) * Fix SQL injection in SQL Agent plugin via parameterized queries Replace string concatenation with parameterized queries in all database connectors to prevent SQL injection through LLM-generated table names. Changes: - PostgreSQL: Use $1, $2 placeholders with pg client parameterization - MySQL: Use ? placeholders with mysql2 execute() prepared statements - MSSQL: Use @p0 placeholders with request.input() parameterization - Update handlers to support parameterized query objects - Add formatQueryForDisplay() for logging parameterized queries Security: Mitigates potential SQL injection when LLM passes unsanitized user input as table_name parameter to getTableSchemaSql/getTablesSql. GHSA-jwjx-mw2p-5wc7 * Align Manager API access with frontend access GHSA-wfq3-65gm-3g2p * Enforce user suspension check on browser extension API key path Previously, suspended users could continue using browser extension endpoints if they had created an API key before suspension. The normal JWT session path blocked suspended users, but the browser extension middleware did not. Changes: - Add suspension and user existence checks to validBrowserExtensionApiKey - Delete browser extension API keys when a user is deleted - Add deleteAllForUser method to BrowserExtensionApiKey model GHSA-7754-8jcc-2rg3 * Fix potential Zip Slip path traversal in community plugin import Validate all ZIP entries before extraction in importCommunityItemFromUrl() to prevent path traversal attacks (CWE-22). Malicious ZIP entries with paths like "../../" could write files outside the intended plugin folder. Requires admin privileges and explicit opt-in to unverified hub downloads. GHSA-rh66-4w74-cf4m * Remove `WelcomeMessages` from app - no longer used (#5206) * remove `WelcomeMessages` from app - no longer user * update erronous alert message * fix job collision ref * fix jobs - remove dev job * Fix potential IDOR vulnerability in workspace parsed files endpoints Add ownership validation to prevent users from deleting or embedding parsed files that don't belong to them. Previously, the delete and embed endpoints only validated authentication but not resource ownership, allowing users to delete attached files for users within workspaces they are also a member of. Changes: - Delete endpoint now filters by userId and workspaceId - Embed endpoint validates file belongs to user and workspace (redundant) - delete() returns false when no matching records found (returns 403) - Added JSDoc comments for clarity GHSA-p5rf-8p88-979c * add user id to chat feedback update JSDOC on middleware for typedef GHSA-2qmm-82f7-8qj5 * feat: Add document count indicators to workspace document management modal (#5207) * add document counts to non-embedded and embedded documents * Update logic to not count search filtered documents * refactor how count is done and rendered * translations --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * feat(agents): Add Perplexity Search API as web search provider (#5210) * feat(agents): Add Perplexity Search API as web search provider Adds Perplexity as a search provider for the agent web-browsing plugin, using the Perplexity Search API (POST /search) which returns raw ranked web results — distinct from the existing Perplexity LLM integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: replace docs.perplexity.ai with console.perplexity.ai * chore: replace docs.perplexity.ai with console.perplexity.ai --------- Co-authored-by: kesku <kesku@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * bump version tag 1.11.2 * update exa search provider description (#5225) * update exa search provider description Co-Authored-By: ishan <ishan@exa.ai> * update exa search provider description Co-Authored-By: ishan <ishan@exa.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ishan <ishan@exa.ai> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Automatic mode for workspace (Agent mode default) (#5143) * Add automatic chat mode with native tool calling support Introduces a new automatic chat mode (now the default) that automatically invokes tools when the provider supports native tool calling. Conditionally shows/hides the @agent command based on whether native tooling is available. - Add supportsNativeToolCalling() to AI providers (OpenAI, Anthropic, Azure always support; others opt-in via ENV) - Update all locale translations with new mode descriptions - Enhance translator to preserve Trans component tags - Remove deprecated ability tags UI * rebase translations * WIP on image attachments. Supports initial image attachment + subsequent attachments * persist images * Image attachments and updates for providers * desktop pre-change * always show command on failure * add back gemini streaming detection * move provider native tooling flag to Provider func * whoops - forgot to delete * strip "@agent" from prompts to prevent weird replies * translations for automatic-mode (#5145) * translations for automatic-mode * rebase * translations * lint * fix dead translations * change default for now to chat mode just for rollout * remove pfp for workspace * passthrough workspace for showAgentCommand detection and rendering * Agent API automatic mode support * ephemeral attachments passthrough * support reading of pinned documents in agent context * MCP tool manager (#5230) * MCP tool manager * Mcp tool manager i18 (#5231) i18n translations for MCP manager changes connect #5230 * fix bad i18n key * Intelligent Skill Selection (#5236) * Beta Intelligent Tooling todo: Agent Skill banner warning when tool # is high or % of content window? * forgot files * add UI controls and maxToolCallStack setting * update docs link * ISS i18n (#5237) i18n * README updates (#5238) * README updates * Update README.md * Update README.md * remove unused images * updates * copy updates * fix(collector): infer file extension from Content-Type for URLs without explicit extensions (#5252) * fix(collector): infer file extension from Content-Type for URLs without explicit extensions When downloading files from URLs like https://arxiv.org/pdf/2307.10265, the path has no recognizable file extension. The downloaded file gets saved without an extension (or with a nonsensical one like .10265), causing processSingleFile to reject it with 'File extension .10265 not supported for parsing'. Fix: after downloading, check if the filename has a supported file extension. If not, inspect the response Content-Type header and map it to the correct extension using the existing ACCEPTED_MIMES table. For example, a response with Content-Type: application/pdf will cause the file to be saved with a .pdf extension, allowing it to be processed correctly. Fixes #4513 * small refactor --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * feat: add Lithuanian locale and register in resources (#5243) * feat: add Lithuanian locale and register in resources * sync --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Telegram bot connector (#5190) * wip telegram bot connector * encrypt bot token, reorg telegram bot modules, secure pairing codes * offload telegram chat to background worker, add @agent support with chart png rendering, reconnect ui * refactor telegram bot settings page into subcomponents * response.locals for mum, telemetry for connecting to telegram * simplify telegram command registration * improve telegram bot ux: rework switch/history/resume commands * add voice, photo, and TTS support to telegram bot with long message handling * lint * rename external_connectors to external_communication_connectors, add voice response mode, persist chat workspace/thread selection * lint * fix telegram bot connect/disconnect bugs, kill telegram bot on multiuser mode enable * add english translations * fix qr code in light mode * repatch migration * WIP checkpoint * pipeline overhaul for using response obj * format functions * fix comment block * remove conditional dumpENV + lint * remove .end() from sendStatus calls * patch broken streaming where streaming only first chunk * refactor * use Ephemeral handler now * show metrics and citations in real GUI * bugfixes * prevent MuM persistence, UI cleanup, styling for status * add new workspace flow in UI Add thread chat count fix 69 byte payload callback limit bug * handle pagination for workspaces, threads, and models * modularize commands and navigation * add /proof support for citation recall * handle backlog message spam * support abort of response streams * code cleanup * spam prevention * fix translations, update voice typing indicator, fix token bug * frontend refactor, update tips on /status and voice response improvements * collapse agent though blocks * support images * Fix mime issues with audio from other devices * fix config issue post server stop * persist image on agentic chats * 5189 i18n (#5245) * i18n translations connect #5189 * prune translations * fix errors * fix translation gaps --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Add User-Agent header for Anthropic API calls (#5174) * Add User-Agent header for Anthropic API calls Passes User-Agent: AnythingLLM/{version} to the Anthropic SDK so Anthropic can identify traffic from AnythingLLM. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove test, simplify header default * unset change to spread --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * add Dynamic `max_tokens` retreival for Anthropic models (#5255) * fix Firefox LaTeX rendering (#5258) * fix pruned translations * whitelist valid dynamic translation * add ask to run prompt for tool calls (demo) (#5261) * add ask to run prompt for tools * border-none on buttons * translations * linting * i18n (#5263) * extend approve/deny requests to telegram * break up handler * Refactor onboarding welcome screen to v2 design (#5262) * refactor onboarding home page to v2 design * fixc typography and buttons * refactor useTheme to return isLight variable | call useTheme from inside SVG component | apply light mode background gradient | polish styles to match designs * add welcome i18n * simplify isLight variable * add new welcome translation key to locales * delete unused images * move OnboardingLogoSVG into module | compute isLight directly in component * add type button | add border-none | add hover state * update hook with doc --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Filesystem Agent Skill overhaul (#5260) * wip * collector parse fixes * refactor for class and also operation for reading * add skill management panel * management panel + lint * management panel + lint * Hide skill in non-docker context * add ask-prompt for edit tool calls * fix dep * fix execa pkg (unused in codebase) * simplify search with ripgrep only and build deps * Fs skill i18n (#5264) i18n * add copy file support * fix translations * fix es translation entry * feat : auto-select newly uploaded docs/URLs in my documents list (#5222) * auto-select newly uploaded docs/URLs in My Documents list * fix: improve auto-select reliability and fix debounce/selection bugs - Add missing `await` on fetchKeys in handleSendLink so loading state and auto-select timing work correctly - Use functional update for setSelectedItems to merge with existing selections instead of replacing them - Stabilize debounced fetchKeys with useRef so rapid uploads actually debounce instead of creating independent timers per render - Rename shadowed local variables (availableDocs -> filteredAvailableDocs) for clarity --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * remove legacy cost estimate for embedding * feat: add missing Lemonade LLM provider env vars to .env.example (#5275) add llm provider lemonade env vars to .env.example * fix openapi spec * feat: add optional API key support for Lemonade provider (#5281) * add API key param to Lemonade LLM Provider and Embedding Provider * add LEMONADE_LLM_API_KEY to .env.example * add api key to aibitat provider * fix api key from being sent to frontend * fix tooltip id * add null fallback for `apiKey` * remove console log * add missing api keys --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * File creation agent skills (#5280) * Powerpoint File Creation (#5278) * wip * download card * UI for downloading * move to fs system with endpoint to pull files * refactor UI * final-pass * remove save-file-browser skill and refactor * remove fileDownload event * reset * reset file * reset timeout * persist toggle * Txt creation (#5279) * wip * download card * UI for downloading * move to fs system with endpoint to pull files * refactor UI * final-pass * remove save-file-browser skill and refactor * remove fileDownload event * reset * reset file * reset timeout * wip * persist toggle * add arbitrary text creation file * Add PDF document generation with markdown formatting (#5283) add support for branding in bottom right corner refactor core utils and frontend rendering * Xlsx document creation (#5284) add Excel doc & sheet creation * Basic docx creation (#5285) * Basic docx creation * add test theme support + styling and title pages * simplify skill selection * handle TG attachments * send documents over tg * lazy import * pin deps * fix lock * i18n for file creation (#5286) i18n for file-creation connect #5280 * theme overhaul * Add PPTX subagent for better results * forgot files * Add PPTX subagent for better results (#5287) * Add PPTX subagent for better results * forgot files * make sub-agent use proper tool calling if it can and better UI hints * add batching Intelligent Tool Selector for performance and scoring * Automatic mode is now default * show links in /proof on TG * Redesign Telegram bot settings UI (#5306) * redesign telegram bot settings ui/refactor ui components * fix positioning of user row * move ConnectedBotCard to subcomponent * fix redirect * remove redundant guard --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * remove log * Fix chat UI event listener bloat (#5323) * 1.12.0 release (#5331) * German translation fixes (#5319) * Fix German login welcome message * More German translation fixes --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix(lemonade): throw on embedding failures instead of returning empty (#5325) * fix(lemonade): throw on embedding failures instead of returning empty vectors * use class logger --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Fix light mode docgen page (#5347) Fix light mode docgen * fix(agent-flows): keep flow menu visible in narrow windows (#5341) * fix(agent-flows): keep flow menu visible in narrow windows * fix(agent-flows): prevent gear menu text clipping Signed-off-by: suyua9 <1521777066@qq.com> --------- Signed-off-by: suyua9 <1521777066@qq.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Fix Agent Flow toggle state sync (#5348) * hide cluttered menus on small screens resolves #5055 closes #5132 * admin only flow-plugin path validation * Remove illegal chars for Windows on files (#5364) * add provider/embedder to bug report for clarity resolves #5363 * add provider/embedder to bug report for clarity resolves #5363 * Revert "Remove illegal chars for Windows on files (#5364)" This reverts commit8ed1d35ab3. * Reapply "Remove illegal chars for Windows on files (#5364)" This reverts commit869be87ef6. * feat: Document Embedding Status Events | Refactor Document Embedding to Job Queue and Forked Process (#5254) * implement native embedder job queue * persist embedding progress across renders * add development worker timeouts * change to static method * native reranker * remove useless return * lint * simplify * make embedding worker timeout value configurable by admin * add event emission for missing data * lint * remove onProgress callback argument * make rerank to rerankDirect * persists progress state across app reloads * remove chunk level progress reporting * remove unuse dvariable * make NATIVE_RERANKING_WORKER_TIMEOUT user configurable * remove dead code * scope embedding progress per-user and clear stale state on SSE reconnect * lint * revert vector databases and embedding engines to call their original methods * simplify rerank * simplify progress fetching by removing updateProgressFromApi * remove duplicate jsdoc * replace sessionStorage persistence with server-side history replay for embedding progress * fix old comment * fix: ignore premature SSE all_complete when embedding hasn't started yet The SSE connection opens before the embedding API call fires, so the server sees no buffered history and immediately sends all_complete. Firefox dispatches this eagerly enough that it closes the EventSource before real progress events arrive, causing the progress UI to clear and fall back to the loading spinner. Chrome's EventSource timing masks the race. Track slugs where startEmbedding was called but no real progress event has arrived yet via awaitingProgressRef. Ignore the first all_complete for those slugs and keep the connection open for the real events. * reduce duplication with progress emissions * remove dead code * refactor: streamline embedding progress handling Removed unnecessary tracking of slugs for premature all_complete events in the EmbeddingProgressProvider. Updated the server-side logic to avoid sending all_complete when no embedding is in progress, allowing the connection to remain open for real events. Adjusted the embedding initiation flow to ensure the server processes the job before the SSE connection opens, improving the reliability of progress updates. * fix stale comment * remove unused function * fix event emissions for document creation failure * refactor: move Reranking Worker Idle Timeout input to LanceDBOptions component Extracted the Reranking Worker Idle Timeout input from GeneralEmbeddingPreference and integrated it into the LanceDBOptions component. This change enhances modularity and maintains a cleaner structure for the settings interface. * lint * remove unused hadHistory vars * refactor workspace directory by hoisting component and converting into functions * moved EmbeddingProgressProvider to wrap Document Manager Modal * refactor embed progress SSE connection to use fetchEventSource instead of native EventSource API. * refactor message handlng into a function and reduce duplication * refactor: utilize writeResponseChunk for event emissions in document embedding progress SSE * refactor: explicit in-proc embedding and rerank methods that are called by workers instead of process.send checks * Abstract EmbeddingProgressBus and Worker Queue into modules * remove error and toast messages on embed process result * use safeJsonParse * add chunk-level progress events with per-document progress bar in UI * remove unused parameter * rename all worker timeout references to use ttl | remove ttl updating from UI * refactor: pass embedding context through job payload instead of global state * lint * add graceful shutdown for workers * apply figma styles * refactor embedding worker to use bree * use existing WorkerQueue class as the management layer for jobs * lint * revert all reranking worker changes back to master state Removes the reranking worker queue, rerankViaWorker/rerankInProcess renames, and NATIVE_RERANKING_WORKER_TTL config so this branch only contains the embedding worker job queue feature. * remove breeManaged flag — WorkerQueue always spawns via Bree * fix prompt embedding bug * have embedTextInput call embedChunksInProcess * add message field to `process.send()` * remove nullish check and error throw * remove bespoke graceful shutdown logix * add spawnWorker method and asbtract redudant flows into helper methods * remove unneeded comment * remove recomputation of TTL value * frontend cleanup and refactor * wip on backend refactor * backend overhaul * small lint * second pass * add logging, update endpoint * simple refactor * add reporting to all embedder providers * fix styles --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Update Lemonade Integration to support v10.1.0 changes (#5378) Update Lemonade Integraion Fix ApiKey nullification check causing hard throw * Enable final tool call in MAX_STACK to run (#5381) * Fix streaming issue for LLM instruction blocks (#5382) * Fix Telegram thread being null, actually wait for disconnect to prevent conflict at runtime * Add retry handling to TG for transient failures (#5391) * Add retry handling to TG for transient failures * add async to promise * Migrate to org-maintained mdpdf for lang support (Hangul, Simplified Chinese, Kanji) (#5392) move to custom mdpdf for lang support (Hangul, Simplified Chinese) * Update TG Transient error code and unclosed tag handler * feat: adds name field to api keys (#5366) * feat: adds name field to api keys * remove extra toasts * prune and norm translations --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Add automatic agent skill aproval via ENV Flag (#5405) * add autoapproval env flag * persist flag * GMail Agent Skill (#5400) * wip * remove label tech * ask to read attachments * update skills * Skill ready and tested * report dynamic citations and generic get mailbox util * norm translations * translations * remove dead code, remove connector in multiUser * simple refactor - dont ask for drafts * refactor filesize helper * norm translations, remove read_messages skill * Helm chart updates (#5410) * move strategy to deployment spec Signed-off-by: Busta Pipes <busta.pipes@gmail.com> * add optional httproute resource Signed-off-by: Busta Pipes <busta.pipes@gmail.com> --------- Signed-off-by: Busta Pipes <busta.pipes@gmail.com> Co-authored-by: Busta Pipes <busta.pipes@gmail.com> * feat: add Catalan translation (#5411) * Add Catalan translation * lint --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: preserve Confluence context paths (#5415) * fix: preserve confluence context paths * lint and minor changes --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * Enable chatId reporting during agent sessions (#5407) * 5427 translations (#5429) * Outlook agent via Entra Application * translations * Revert "5427 translations (#5429)" This reverts commit4172751858. * Outlook agent via Entra Application (#5427) * Outlook agent via Entra Application * translations (#5437) * reorder skills for app integrations * Refactor Gmail Agent (#5439) * make DDG default web-search in UI (already is in backend!) * Google calendar skill (#5442) * Google Calendar Agent * forgot files * Translations (#5443) * Image lightbox for chat attachments (#5441) * add image lightbox for chat attachments * wrap lightbox image triggers in button elements * add images to dependency array * add jsdoc to ChatAttachments and remove filter * fix regenerate from system message connect #5407 * dedupe email items based on name * comment on outlook agent * Better citations for gmail, gcal, and outlook * bump TG edit to prevent edit spam for messages since edits count as a send event and too many will result in a 429 resolves #5447 * Merge commit from fork * better special citation styling * Add capability detection and streaming usage for Generic OpenAI provider (#5477) - Add ENV-configurable model capabilities (tools, reasoning, vision, imageGeneration) via PROVIDER_SUPPORTS_* environment variables - Add optional stream usage reporting via GENERIC_OPEN_AI_REPORT_USAGE - Fix streaming tool calls for providers that send null tool_call.id (e.g., mlx-server) by generating fallback UUIDs - Refactor supportsNativeToolCalling() to use centralized capabilities API * fix: omit temperature param for Bedrock Claude Opus 4.7 (#5472) * addconditionally pass temperature based on aws bedrock model id * move to config --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: long-prompt bubble flicker & See More collapse on streaming/scroll (#5473) fix ui flickering and truncatable prompt expansion bug Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * fix: surface readable error messages in web-scraping agent and ai-provider (#5476) * fix: surface readable error messages in web-scraping agent and ai-provider * simplify --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com> * update tool call response to always include convo ID for emails so they are not hallunicated * sync locales * ensure db schema * 1.12.1 release tags (#5483) * bump pg tag --------- Signed-off-by: suyua9 <1521777066@qq.com> Signed-off-by: Busta Pipes <busta.pipes@gmail.com> Co-authored-by: Dipanshu Rawat <dv451197@gmail.com> Co-authored-by: Marcello Fitton <106866560+angelplusultra@users.noreply.github.com> Co-authored-by: shatfield4 <seanhatfield5@gmail.com> Co-authored-by: Brian Pursley <bpursley@cinlogic.com> Co-authored-by: Morgan <morgan@scifoundry.com> Co-authored-by: Morgan Giddings <morgan@MG-Mac-Studio.home.arpa> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ryan <RALaBarge@users.noreply.github.com> Co-authored-by: Maxwell Calkin <101308415+MaxwellCalkin@users.noreply.github.com> Co-authored-by: Peter Dave Hello <hsu@peterdavehello.org> Co-authored-by: Peter Dave Hello <3691490+PeterDaveHello@users.noreply.github.com> Co-authored-by: Kesku <62210496+kesku@users.noreply.github.com> Co-authored-by: kesku <kesku@users.noreply.github.com> Co-authored-by: Ishan Goswami <ishangoswami315@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ishan <ishan@exa.ai> Co-authored-by: Yitong Li <yitongli0814@gmail.com> Co-authored-by: arvydev <55648027+arvydev@users.noreply.github.com> Co-authored-by: Mike Lambert <mlambert@gmail.com> Co-authored-by: Neha Prasad <neh6a683@gmail.com> Co-authored-by: S. Neuhaus <neuhaus@users.noreply.github.com> Co-authored-by: suyua9 <1521777066@qq.com> Co-authored-by: Guilherme Nogueira <guilherme.lopesn@gmail.com> Co-authored-by: Kurt <bustapipes@users.noreply.github.com> Co-authored-by: Busta Pipes <busta.pipes@gmail.com> Co-authored-by: Jordi Mas <jmas@softcatala.org> Co-authored-by: Asish Kumar <87874775+officialasishkumar@users.noreply.github.com> Co-authored-by: Akhil <133588800+Akhil373@users.noreply.github.com>
1500 lines
45 KiB
JavaScript
1500 lines
45 KiB
JavaScript
process.env.NODE_ENV === "development"
|
|
? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
|
|
: require("dotenv").config();
|
|
const { viewLocalFiles, normalizePath, isWithin } = require("../utils/files");
|
|
const { purgeDocument, purgeFolder } = require("../utils/files/purgeDocument");
|
|
const { getVectorDbClass } = require("../utils/helpers");
|
|
const { updateENV, dumpENV } = require("../utils/helpers/updateENV");
|
|
const {
|
|
reqBody,
|
|
makeJWT,
|
|
userFromSession,
|
|
multiUserMode,
|
|
queryParams,
|
|
} = require("../utils/http");
|
|
const { handleAssetUpload, handlePfpUpload } = require("../utils/files/multer");
|
|
const { v4 } = require("uuid");
|
|
const { SystemSettings } = require("../models/systemSettings");
|
|
const { User } = require("../models/user");
|
|
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const {
|
|
getDefaultFilename,
|
|
determineLogoFilepath,
|
|
fetchLogo,
|
|
validFilename,
|
|
renameLogoFile,
|
|
removeCustomLogo,
|
|
LOGO_FILENAME,
|
|
isDefaultFilename,
|
|
} = require("../utils/files/logo");
|
|
const { Telemetry } = require("../models/telemetry");
|
|
const { ApiKey } = require("../models/apiKeys");
|
|
const { getCustomModels } = require("../utils/helpers/customModels");
|
|
const { WorkspaceChats } = require("../models/workspaceChats");
|
|
const {
|
|
flexUserRoleValid,
|
|
ROLES,
|
|
isMultiUserSetup,
|
|
} = require("../utils/middleware/multiUserProtected");
|
|
const { fetchPfp, determinePfpFilepath } = require("../utils/files/pfp");
|
|
const { exportChatsAsType } = require("../utils/helpers/chat/convertTo");
|
|
const { EventLogs } = require("../models/eventLogs");
|
|
const { CollectorApi } = require("../utils/collectorApi");
|
|
const {
|
|
recoverAccount,
|
|
resetPassword,
|
|
generateRecoveryCodes,
|
|
} = require("../utils/PasswordRecovery");
|
|
const { SlashCommandPresets } = require("../models/slashCommandsPresets");
|
|
const { EncryptionManager } = require("../utils/EncryptionManager");
|
|
const { BrowserExtensionApiKey } = require("../models/browserExtensionApiKey");
|
|
const {
|
|
chatHistoryViewable,
|
|
} = require("../utils/middleware/chatHistoryViewable");
|
|
const {
|
|
simpleSSOEnabled,
|
|
simpleSSOLoginDisabled,
|
|
} = require("../utils/middleware/simpleSSOEnabled");
|
|
const { TemporaryAuthToken } = require("../models/temporaryAuthToken");
|
|
const { SystemPromptVariables } = require("../models/systemPromptVariables");
|
|
const { VALID_COMMANDS } = require("../utils/chats");
|
|
const { AgentSkillWhitelist } = require("../models/agentSkillWhitelist");
|
|
|
|
function systemEndpoints(app) {
|
|
if (!app) return;
|
|
|
|
app.get("/ping", (_, response) => {
|
|
response.status(200).json({ online: true });
|
|
});
|
|
|
|
app.get("/migrate", async (_, response) => {
|
|
response.sendStatus(200);
|
|
});
|
|
|
|
app.get("/env-dump", async (_, response) => {
|
|
if (process.env.NODE_ENV !== "production")
|
|
return response.sendStatus(200).end();
|
|
dumpENV();
|
|
response.sendStatus(200).end();
|
|
});
|
|
|
|
app.get("/onboarding", async (_, response) => {
|
|
try {
|
|
const results = await SystemSettings.isOnboardingComplete();
|
|
response.status(200).json({ onboardingComplete: results });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.post("/onboarding", [validatedRequest], async (_, response) => {
|
|
try {
|
|
await SystemSettings.markOnboardingComplete();
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get("/setup-complete", async (_, response) => {
|
|
try {
|
|
const results = await SystemSettings.currentSettings();
|
|
response.status(200).json({ results });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/check-token",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (multiUserMode(response)) {
|
|
const user = await userFromSession(request, response);
|
|
if (!user || user.suspended) {
|
|
response.sendStatus(403).end();
|
|
return;
|
|
}
|
|
|
|
response.sendStatus(200).end();
|
|
return;
|
|
}
|
|
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Refreshes the user object from the session from a provided token.
|
|
* This does not refresh the token itself - if that is expired or invalid, the user will be logged out.
|
|
* This simply keeps the user object in sync with the database over the course of the session.
|
|
* @returns {Promise<{success: boolean, user: Object | null, message: string | null}>}
|
|
*/
|
|
app.get(
|
|
"/system/refresh-user",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (!multiUserMode(response))
|
|
return response
|
|
.status(200)
|
|
.json({ success: true, user: null, message: null });
|
|
|
|
const user = await userFromSession(request, response);
|
|
if (!user)
|
|
return response.status(200).json({
|
|
success: false,
|
|
user: null,
|
|
message: "Session expired or invalid.",
|
|
});
|
|
|
|
if (user.suspended)
|
|
return response.status(200).json({
|
|
success: false,
|
|
user: null,
|
|
message: "User is suspended.",
|
|
});
|
|
|
|
return response.status(200).json({
|
|
success: true,
|
|
user: User.filterFields(user),
|
|
message: null,
|
|
});
|
|
} catch (e) {
|
|
return response.status(500).json({
|
|
success: false,
|
|
user: null,
|
|
message: e.message,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post("/request-token", async (request, response) => {
|
|
try {
|
|
const bcrypt = require("bcryptjs");
|
|
|
|
if (await SystemSettings.isMultiUserMode()) {
|
|
if (simpleSSOLoginDisabled()) {
|
|
response.status(403).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message:
|
|
"[005] Login via credentials has been disabled by the administrator.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { username, password } = reqBody(request);
|
|
const existingUser = await User._get({ username: String(username) });
|
|
|
|
if (!existingUser) {
|
|
await EventLogs.logEvent(
|
|
"failed_login_invalid_username",
|
|
{
|
|
ip: request.ip || "Unknown IP",
|
|
username: username || "Unknown user",
|
|
},
|
|
existingUser?.id
|
|
);
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[001] Invalid login credentials.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!bcrypt.compareSync(String(password), existingUser.password)) {
|
|
await EventLogs.logEvent(
|
|
"failed_login_invalid_password",
|
|
{
|
|
ip: request.ip || "Unknown IP",
|
|
username: username || "Unknown user",
|
|
},
|
|
existingUser?.id
|
|
);
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[002] Invalid login credentials.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (existingUser.suspended) {
|
|
await EventLogs.logEvent(
|
|
"failed_login_account_suspended",
|
|
{
|
|
ip: request.ip || "Unknown IP",
|
|
username: username || "Unknown user",
|
|
},
|
|
existingUser?.id
|
|
);
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[004] Account suspended by admin.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
await Telemetry.sendTelemetry(
|
|
"login_event",
|
|
{ multiUserMode: false },
|
|
existingUser?.id
|
|
);
|
|
|
|
await EventLogs.logEvent(
|
|
"login_event",
|
|
{
|
|
ip: request.ip || "Unknown IP",
|
|
username: existingUser.username || "Unknown user",
|
|
},
|
|
existingUser?.id
|
|
);
|
|
|
|
// Generate a session token for the user then check if they have seen the recovery codes
|
|
// and if not, generate recovery codes and return them to the frontend.
|
|
const sessionToken = makeJWT(
|
|
{ id: existingUser.id, username: existingUser.username },
|
|
process.env.JWT_EXPIRY
|
|
);
|
|
if (!existingUser.seen_recovery_codes) {
|
|
const plainTextCodes = await generateRecoveryCodes(existingUser.id);
|
|
response.status(200).json({
|
|
valid: true,
|
|
user: User.filterFields(existingUser),
|
|
token: sessionToken,
|
|
message: null,
|
|
recoveryCodes: plainTextCodes,
|
|
});
|
|
return;
|
|
}
|
|
|
|
response.status(200).json({
|
|
valid: true,
|
|
user: User.filterFields(existingUser),
|
|
token: sessionToken,
|
|
message: null,
|
|
});
|
|
return;
|
|
} else {
|
|
const { password } = reqBody(request);
|
|
if (
|
|
!bcrypt.compareSync(
|
|
password,
|
|
bcrypt.hashSync(process.env.AUTH_TOKEN, 10)
|
|
)
|
|
) {
|
|
await EventLogs.logEvent("failed_login_invalid_password", {
|
|
ip: request.ip || "Unknown IP",
|
|
multiUserMode: false,
|
|
});
|
|
response.status(401).json({
|
|
valid: false,
|
|
token: null,
|
|
message: "[003] Invalid password provided",
|
|
});
|
|
return;
|
|
}
|
|
|
|
await Telemetry.sendTelemetry("login_event", { multiUserMode: false });
|
|
await EventLogs.logEvent("login_event", {
|
|
ip: request.ip || "Unknown IP",
|
|
multiUserMode: false,
|
|
});
|
|
response.status(200).json({
|
|
valid: true,
|
|
token: makeJWT(
|
|
{ p: new EncryptionManager().encrypt(password) },
|
|
process.env.JWT_EXPIRY
|
|
),
|
|
message: null,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/request-token/sso/simple",
|
|
[simpleSSOEnabled],
|
|
async (request, response) => {
|
|
const { token: tempAuthToken } = request.query;
|
|
const { sessionToken, token, error } =
|
|
await TemporaryAuthToken.validate(tempAuthToken);
|
|
|
|
if (error) {
|
|
await EventLogs.logEvent("failed_login_invalid_temporary_auth_token", {
|
|
ip: request.ip || "Unknown IP",
|
|
multiUserMode: true,
|
|
});
|
|
return response.status(401).json({
|
|
valid: false,
|
|
token: null,
|
|
message: `[001] An error occurred while validating the token: ${error}`,
|
|
});
|
|
}
|
|
|
|
await Telemetry.sendTelemetry(
|
|
"login_event",
|
|
{ multiUserMode: true },
|
|
token.user.id
|
|
);
|
|
await EventLogs.logEvent(
|
|
"login_event",
|
|
{
|
|
ip: request.ip || "Unknown IP",
|
|
username: token.user.username || "Unknown user",
|
|
},
|
|
token.user.id
|
|
);
|
|
|
|
response.status(200).json({
|
|
valid: true,
|
|
user: User.filterFields(token.user),
|
|
token: sessionToken,
|
|
message: null,
|
|
});
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/recover-account",
|
|
[isMultiUserSetup],
|
|
async (request, response) => {
|
|
try {
|
|
const { username, recoveryCodes } = reqBody(request);
|
|
const { success, resetToken, error } = await recoverAccount(
|
|
username,
|
|
recoveryCodes
|
|
);
|
|
|
|
if (success) {
|
|
response.status(200).json({ success, resetToken });
|
|
} else {
|
|
response.status(400).json({ success, message: error });
|
|
}
|
|
} catch (error) {
|
|
console.error("Error recovering account:", error);
|
|
response
|
|
.status(500)
|
|
.json({ success: false, message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/reset-password",
|
|
[isMultiUserSetup],
|
|
async (request, response) => {
|
|
try {
|
|
const { token, newPassword, confirmPassword } = reqBody(request);
|
|
const { success, message, error } = await resetPassword(
|
|
token,
|
|
newPassword,
|
|
confirmPassword
|
|
);
|
|
|
|
if (success) {
|
|
response.status(200).json({ success, message });
|
|
} else {
|
|
response.status(400).json({ success, error });
|
|
}
|
|
} catch (error) {
|
|
console.error("Error resetting password:", error);
|
|
response.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/system-vectors",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (request, response) => {
|
|
try {
|
|
const query = queryParams(request);
|
|
const VectorDb = getVectorDbClass();
|
|
const vectorCount = !!query.slug
|
|
? await VectorDb.namespaceCount(query.slug)
|
|
: await VectorDb.totalVectors();
|
|
response.status(200).json({ vectorCount });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/remove-document",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (request, response) => {
|
|
try {
|
|
const { name } = reqBody(request);
|
|
await purgeDocument(name);
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/remove-documents",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (request, response) => {
|
|
try {
|
|
const { names } = reqBody(request);
|
|
for await (const name of names) await purgeDocument(name);
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/remove-folder",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (request, response) => {
|
|
try {
|
|
const { name } = reqBody(request);
|
|
await purgeFolder(name);
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/local-files",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (_, response) => {
|
|
try {
|
|
const localFiles = await viewLocalFiles();
|
|
response.status(200).json({ localFiles });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/document-processing-status",
|
|
[validatedRequest],
|
|
async (_, response) => {
|
|
try {
|
|
const online = await new CollectorApi().online();
|
|
response.sendStatus(online ? 200 : 503);
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/accepted-document-types",
|
|
[validatedRequest],
|
|
async (_, response) => {
|
|
try {
|
|
const types = await new CollectorApi().acceptedFileTypes();
|
|
if (!types) {
|
|
response.sendStatus(404).end();
|
|
return;
|
|
}
|
|
|
|
response.status(200).json({ types });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/update-env",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const body = reqBody(request);
|
|
const { newValues, error } = await updateENV(
|
|
body,
|
|
false,
|
|
response?.locals?.user?.id
|
|
);
|
|
response.status(200).json({ newValues, error });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/update-password",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
// Cannot update password in multi - user mode.
|
|
if (multiUserMode(response)) {
|
|
response.sendStatus(401).end();
|
|
return;
|
|
}
|
|
|
|
let error = null;
|
|
const { usePassword, newPassword } = reqBody(request);
|
|
if (!usePassword) {
|
|
// Password is being disabled so directly unset everything to bypass validation.
|
|
process.env.AUTH_TOKEN = "";
|
|
process.env.JWT_SECRET = "";
|
|
} else {
|
|
error = await updateENV(
|
|
{
|
|
AuthToken: newPassword,
|
|
JWTSecret: v4(),
|
|
},
|
|
true
|
|
)?.error;
|
|
}
|
|
response.status(200).json({ success: !error, error });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/enable-multi-user",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
response.status(200).json({
|
|
success: false,
|
|
error: "Multi-user mode is already enabled.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { username, password } = reqBody(request);
|
|
const { user, error } = await User.create({
|
|
username,
|
|
password,
|
|
role: ROLES.admin,
|
|
});
|
|
|
|
if (error || !user) {
|
|
response.status(400).json({
|
|
success: false,
|
|
error: error || "Failed to enable multi-user mode.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
await SystemSettings._updateSettings({
|
|
multi_user_mode: true,
|
|
});
|
|
await BrowserExtensionApiKey.migrateApiKeysToMultiUser(user.id);
|
|
await AgentSkillWhitelist.clearSingleUserWhitelist();
|
|
await updateENV(
|
|
{
|
|
JWTSecret: process.env.JWT_SECRET || v4(),
|
|
},
|
|
true
|
|
);
|
|
await Telemetry.sendTelemetry("enabled_multi_user_mode", {
|
|
multiUserMode: true,
|
|
});
|
|
await EventLogs.logEvent("multi_user_mode_enabled", {}, user?.id);
|
|
response.status(200).json({ success: !!user, error });
|
|
} catch (e) {
|
|
await User.delete({});
|
|
await SystemSettings._updateSettings({
|
|
multi_user_mode: false,
|
|
});
|
|
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/multi-user-mode", async (_, response) => {
|
|
try {
|
|
const multiUserMode = await SystemSettings.isMultiUserMode();
|
|
response.status(200).json({ multiUserMode });
|
|
} catch (e) {
|
|
console.error(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get("/system/logo", async function (request, response) {
|
|
try {
|
|
const darkMode =
|
|
!request?.query?.theme || request?.query?.theme === "default";
|
|
const defaultFilename = getDefaultFilename(darkMode);
|
|
const logoPath = await determineLogoFilepath(defaultFilename);
|
|
const { found, buffer, size, mime } = fetchLogo(logoPath);
|
|
|
|
if (!found) {
|
|
response.sendStatus(204).end();
|
|
return;
|
|
}
|
|
|
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
|
response.writeHead(200, {
|
|
"Access-Control-Expose-Headers":
|
|
"Content-Disposition,X-Is-Custom-Logo,Content-Type,Content-Length",
|
|
"Content-Type": mime || "image/png",
|
|
"Content-Disposition": `attachment; filename=${path.basename(
|
|
logoPath
|
|
)}`,
|
|
"Content-Length": size,
|
|
"X-Is-Custom-Logo":
|
|
currentLogoFilename !== null &&
|
|
currentLogoFilename !== defaultFilename &&
|
|
!isDefaultFilename(currentLogoFilename),
|
|
});
|
|
response.end(Buffer.from(buffer, "base64"));
|
|
return;
|
|
} catch (error) {
|
|
console.error("Error processing the logo request:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get("/system/footer-data", [validatedRequest], async (_, response) => {
|
|
try {
|
|
const footerData =
|
|
(await SystemSettings.get({ label: "footer_data" }))?.value ??
|
|
JSON.stringify([]);
|
|
response.status(200).json({ footerData: footerData });
|
|
} catch (error) {
|
|
console.error("Error fetching footer data:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get("/system/support-email", [validatedRequest], async (_, response) => {
|
|
try {
|
|
const supportEmail =
|
|
(
|
|
await SystemSettings.get({
|
|
label: "support_email",
|
|
})
|
|
)?.value ?? null;
|
|
response.status(200).json({ supportEmail: supportEmail });
|
|
} catch (error) {
|
|
console.error("Error fetching support email:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
// No middleware protection in order to get this on the login page
|
|
app.get("/system/custom-app-name", async (_, response) => {
|
|
try {
|
|
const customAppName =
|
|
(
|
|
await SystemSettings.get({
|
|
label: "custom_app_name",
|
|
})
|
|
)?.value ?? null;
|
|
response.status(200).json({ customAppName: customAppName });
|
|
} catch (error) {
|
|
console.error("Error fetching custom app name:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/pfp/:id",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async function (request, response) {
|
|
try {
|
|
const { id } = request.params;
|
|
if (response.locals?.user?.id !== Number(id))
|
|
return response.sendStatus(204).end();
|
|
|
|
const pfpPath = await determinePfpFilepath(id);
|
|
if (!pfpPath) return response.sendStatus(204).end();
|
|
|
|
const { found, buffer, size, mime } = fetchPfp(pfpPath);
|
|
if (!found) return response.sendStatus(204).end();
|
|
|
|
response.writeHead(200, {
|
|
"Content-Type": mime || "image/png",
|
|
"Content-Disposition": `attachment; filename=${path.basename(pfpPath)}`,
|
|
"Content-Length": size,
|
|
});
|
|
response.end(Buffer.from(buffer, "base64"));
|
|
return;
|
|
} catch (error) {
|
|
console.error("Error processing the logo request:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/upload-pfp",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
|
|
async function (request, response) {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const uploadedFileName = request.randomFileName;
|
|
if (!uploadedFileName) {
|
|
return response.status(400).json({ message: "File upload failed." });
|
|
}
|
|
|
|
const userRecord = await User.get({ id: user.id });
|
|
const oldPfpFilename = userRecord.pfpFilename;
|
|
if (oldPfpFilename) {
|
|
const storagePath = path.join(__dirname, "../storage/assets/pfp");
|
|
const oldPfpPath = path.join(
|
|
storagePath,
|
|
normalizePath(userRecord.pfpFilename)
|
|
);
|
|
if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
|
|
throw new Error("Invalid path name");
|
|
if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
|
|
}
|
|
|
|
const { success, error } = await User.update(user.id, {
|
|
pfpFilename: uploadedFileName,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Profile picture uploaded successfully."
|
|
: error || "Failed to update with new profile picture.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the profile picture upload:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
app.get(
|
|
"/system/default-system-prompt",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (_, response) => {
|
|
try {
|
|
const defaultSystemPrompt = await SystemSettings.get({
|
|
label: "default_system_prompt",
|
|
});
|
|
|
|
response.status(200).json({
|
|
success: true,
|
|
defaultSystemPrompt:
|
|
defaultSystemPrompt?.value ||
|
|
SystemSettings.saneDefaultSystemPrompt,
|
|
saneDefaultSystemPrompt: SystemSettings.saneDefaultSystemPrompt,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error fetching default system prompt:", error);
|
|
response
|
|
.status(500)
|
|
.json({ success: false, message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/default-system-prompt",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { defaultSystemPrompt } = reqBody(request);
|
|
const { success, error } = await SystemSettings.updateSettings({
|
|
default_system_prompt: defaultSystemPrompt,
|
|
});
|
|
if (!success)
|
|
throw new Error(
|
|
error.message || "Failed to update default system prompt."
|
|
);
|
|
response.status(200).json({
|
|
success: true,
|
|
message: "Default system prompt updated successfully.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating default system prompt:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
message: error.message || "Internal server error",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/remove-pfp",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async function (request, response) {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const userRecord = await User.get({ id: user.id });
|
|
const oldPfpFilename = userRecord.pfpFilename;
|
|
|
|
if (oldPfpFilename) {
|
|
const storagePath = path.join(__dirname, "../storage/assets/pfp");
|
|
const oldPfpPath = path.join(
|
|
storagePath,
|
|
normalizePath(oldPfpFilename)
|
|
);
|
|
if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
|
|
throw new Error("Invalid path name");
|
|
if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
|
|
}
|
|
|
|
const { success, error } = await User.update(user.id, {
|
|
pfpFilename: null,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Profile picture removed successfully."
|
|
: error || "Failed to remove profile picture.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the profile picture removal:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/upload-logo",
|
|
[
|
|
validatedRequest,
|
|
flexUserRoleValid([ROLES.admin, ROLES.manager]),
|
|
handleAssetUpload,
|
|
],
|
|
async (request, response) => {
|
|
if (!request?.file || !request?.file.originalname) {
|
|
return response.status(400).json({ message: "No logo file provided." });
|
|
}
|
|
|
|
if (!validFilename(request.file.originalname)) {
|
|
return response.status(400).json({
|
|
message: "Invalid file name. Please choose a different file.",
|
|
});
|
|
}
|
|
|
|
try {
|
|
const newFilename = await renameLogoFile(request.file.originalname);
|
|
const existingLogoFilename = await SystemSettings.currentLogoFilename();
|
|
await removeCustomLogo(existingLogoFilename);
|
|
|
|
const { success, error } = await SystemSettings._updateSettings({
|
|
logo_filename: newFilename,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Logo uploaded successfully."
|
|
: error || "Failed to update with new logo.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the logo upload:", error);
|
|
response.status(500).json({ message: "Error uploading the logo." });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/is-default-logo", async (_, response) => {
|
|
try {
|
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
|
const isDefaultLogo =
|
|
!currentLogoFilename || currentLogoFilename === LOGO_FILENAME;
|
|
response.status(200).json({ isDefaultLogo });
|
|
} catch (error) {
|
|
console.error("Error processing the logo request:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/remove-logo",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (_request, response) => {
|
|
try {
|
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
|
await removeCustomLogo(currentLogoFilename);
|
|
const { success, error } = await SystemSettings._updateSettings({
|
|
logo_filename: LOGO_FILENAME,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Logo removed successfully."
|
|
: error || "Failed to update with new logo.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the logo removal:", error);
|
|
response.status(500).json({ message: "Error removing the logo." });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/api-keys", [validatedRequest], async (_, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const apiKeys = await ApiKey.where({});
|
|
return response.status(200).json({
|
|
apiKeys,
|
|
error: null,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).json({
|
|
apiKey: null,
|
|
error: "Could not find an API Key.",
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post(
|
|
"/system/generate-api-key",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const { name = null } = reqBody(request);
|
|
const { apiKey, error } = await ApiKey.create(null, name);
|
|
await EventLogs.logEvent(
|
|
"api_key_created",
|
|
{ name: apiKey?.name },
|
|
response?.locals?.user?.id
|
|
);
|
|
return response.status(200).json({
|
|
apiKey,
|
|
error,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).json({
|
|
apiKey: null,
|
|
error: "Error generating api key.",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
// TODO: This endpoint is replicated in the admin endpoints file.
|
|
// and should be consolidated to be a single endpoint with flexible role protection.
|
|
app.delete(
|
|
"/system/api-key/:id",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode)
|
|
return response.sendStatus(401).end();
|
|
const { id } = request.params;
|
|
if (!id || isNaN(Number(id))) return response.sendStatus(400).end();
|
|
|
|
await ApiKey.delete({ id: Number(id) });
|
|
await EventLogs.logEvent(
|
|
"api_key_deleted",
|
|
{ deletedBy: response.locals?.user?.username },
|
|
response?.locals?.user?.id
|
|
);
|
|
return response.status(200).end();
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/custom-models",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { provider, apiKey = null, basePath = null } = reqBody(request);
|
|
const { models, error } = await getCustomModels(
|
|
provider,
|
|
apiKey,
|
|
basePath
|
|
);
|
|
return response.status(200).json({
|
|
models,
|
|
error,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/event-logs",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { offset = 0, limit = 10 } = reqBody(request);
|
|
const logs = await EventLogs.whereWithData({}, limit, offset * limit, {
|
|
id: "desc",
|
|
});
|
|
const totalLogs = await EventLogs.count();
|
|
const hasPages = totalLogs > (offset + 1) * limit;
|
|
|
|
response.status(200).json({ logs: logs, hasPages, totalLogs });
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/event-logs",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (_, response) => {
|
|
try {
|
|
await EventLogs.delete();
|
|
await EventLogs.logEvent(
|
|
"event_logs_cleared",
|
|
{},
|
|
response?.locals?.user?.id
|
|
);
|
|
response.json({ success: true });
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/workspace-chats",
|
|
[
|
|
chatHistoryViewable,
|
|
validatedRequest,
|
|
flexUserRoleValid([ROLES.admin, ROLES.manager]),
|
|
],
|
|
async (request, response) => {
|
|
try {
|
|
const { offset = 0, limit = 20 } = reqBody(request);
|
|
const chats = await WorkspaceChats.whereWithData(
|
|
{},
|
|
limit,
|
|
offset * limit,
|
|
{ id: "desc" }
|
|
);
|
|
const totalChats = await WorkspaceChats.count();
|
|
const hasPages = totalChats > (offset + 1) * limit;
|
|
|
|
response.status(200).json({ chats: chats, hasPages, totalChats });
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/workspace-chats/:id",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
|
|
async (request, response) => {
|
|
try {
|
|
const { id } = request.params;
|
|
Number(id) === -1
|
|
? await WorkspaceChats.delete({}, true)
|
|
: await WorkspaceChats.delete({ id: Number(id) });
|
|
response.json({ success: true, error: null });
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/export-chats",
|
|
[
|
|
chatHistoryViewable,
|
|
validatedRequest,
|
|
flexUserRoleValid([ROLES.manager, ROLES.admin]),
|
|
],
|
|
async (request, response) => {
|
|
try {
|
|
const { type = "jsonl", chatType = "workspace" } = request.query;
|
|
const { contentType, data } = await exportChatsAsType(type, chatType);
|
|
await EventLogs.logEvent(
|
|
"exported_chats",
|
|
{
|
|
type,
|
|
chatType,
|
|
},
|
|
response.locals.user?.id
|
|
);
|
|
response.setHeader("Content-Type", contentType);
|
|
response.status(200).send(data);
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
// Used for when a user in multi-user updates their own profile
|
|
// from the UI.
|
|
app.post("/system/user", [validatedRequest], async (request, response) => {
|
|
try {
|
|
const sessionUser = await userFromSession(request, response);
|
|
const { username, password, bio } = reqBody(request);
|
|
const id = Number(sessionUser.id);
|
|
|
|
if (!id) {
|
|
response.status(400).json({ success: false, error: "Invalid user ID" });
|
|
return;
|
|
}
|
|
|
|
const updates = {};
|
|
// If the username is being changed, validate it.
|
|
// Otherwise, do not attempt to validate it to allow existing users to keep their username if not changing it.
|
|
if (username !== sessionUser.username)
|
|
updates.username = User.validations.username(String(username));
|
|
if (password) updates.password = String(password);
|
|
if (bio) updates.bio = String(bio);
|
|
|
|
if (Object.keys(updates).length === 0) {
|
|
response
|
|
.status(400)
|
|
.json({ success: false, error: "No updates provided" });
|
|
return;
|
|
}
|
|
|
|
const { success, error } = await User.update(id, updates);
|
|
response.status(200).json({ success, error });
|
|
} catch (e) {
|
|
console.error(e);
|
|
response
|
|
.status(500)
|
|
.json({ success: false, error: e.message || "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/slash-command-presets",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (request, response) => {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
|
|
response.status(200).json({ presets: userPresets });
|
|
} catch (error) {
|
|
console.error("Error fetching slash command presets:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/slash-command-presets",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (request, response) => {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const { command, prompt, description } = reqBody(request);
|
|
const formattedCommand = SlashCommandPresets.formatCommand(
|
|
String(command)
|
|
);
|
|
|
|
if (Object.keys(VALID_COMMANDS).includes(formattedCommand)) {
|
|
return response.status(400).json({
|
|
message:
|
|
"Cannot create a preset with a command that matches a system command",
|
|
});
|
|
}
|
|
|
|
const presetData = {
|
|
command: formattedCommand,
|
|
prompt: String(prompt),
|
|
description: String(description),
|
|
};
|
|
|
|
const preset = await SlashCommandPresets.create(user?.id, presetData);
|
|
if (!preset) {
|
|
return response
|
|
.status(500)
|
|
.json({ message: "Failed to create preset" });
|
|
}
|
|
response.status(201).json({ preset });
|
|
} catch (error) {
|
|
console.error("Error creating slash command preset:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/slash-command-presets/:slashCommandId",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (request, response) => {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const { slashCommandId } = request.params;
|
|
const { command, prompt, description } = reqBody(request);
|
|
const formattedCommand = SlashCommandPresets.formatCommand(
|
|
String(command)
|
|
);
|
|
|
|
if (Object.keys(VALID_COMMANDS).includes(formattedCommand)) {
|
|
return response.status(400).json({
|
|
message:
|
|
"Cannot update a preset to use a command that matches a system command",
|
|
});
|
|
}
|
|
|
|
// Valid user running owns the preset if user session is valid.
|
|
const ownsPreset = await SlashCommandPresets.get({
|
|
userId: user?.id ?? null,
|
|
id: Number(slashCommandId),
|
|
});
|
|
if (!ownsPreset)
|
|
return response.status(404).json({ message: "Preset not found" });
|
|
|
|
const updates = {
|
|
command: formattedCommand,
|
|
prompt: String(prompt),
|
|
description: String(description),
|
|
};
|
|
|
|
const preset = await SlashCommandPresets.update(
|
|
Number(slashCommandId),
|
|
updates
|
|
);
|
|
if (!preset) return response.sendStatus(422);
|
|
response.status(200).json({ preset: { ...ownsPreset, ...updates } });
|
|
} catch (error) {
|
|
console.error("Error updating slash command preset:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/slash-command-presets/:slashCommandId",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (request, response) => {
|
|
try {
|
|
const { slashCommandId } = request.params;
|
|
const user = await userFromSession(request, response);
|
|
|
|
// Valid user running owns the preset if user session is valid.
|
|
const ownsPreset = await SlashCommandPresets.get({
|
|
userId: user?.id ?? null,
|
|
id: Number(slashCommandId),
|
|
});
|
|
if (!ownsPreset)
|
|
return response
|
|
.status(403)
|
|
.json({ message: "Failed to delete preset" });
|
|
|
|
await SlashCommandPresets.delete(Number(slashCommandId));
|
|
response.sendStatus(204);
|
|
} catch (error) {
|
|
console.error("Error deleting slash command preset:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/prompt-variables",
|
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
|
async (request, response) => {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const variables = await SystemPromptVariables.getAll(user?.id);
|
|
response.status(200).json({ variables });
|
|
} catch (error) {
|
|
console.error("Error fetching system prompt variables:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: `Failed to fetch system prompt variables: ${error.message}`,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/prompt-variables",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const user = await userFromSession(request, response);
|
|
const { key, value, description = null } = reqBody(request);
|
|
|
|
if (!key || !value) {
|
|
return response.status(400).json({
|
|
success: false,
|
|
error: "Key and value are required",
|
|
});
|
|
}
|
|
|
|
const variable = await SystemPromptVariables.create({
|
|
key,
|
|
value,
|
|
description,
|
|
userId: user?.id || null,
|
|
});
|
|
|
|
response.status(200).json({
|
|
success: true,
|
|
variable,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error creating system prompt variable:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: `Failed to create system prompt variable: ${error.message}`,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.put(
|
|
"/system/prompt-variables/:id",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { id } = request.params;
|
|
const { key, value, description = null } = reqBody(request);
|
|
|
|
if (!key || !value) {
|
|
return response.status(400).json({
|
|
success: false,
|
|
error: "Key and value are required",
|
|
});
|
|
}
|
|
|
|
const variable = await SystemPromptVariables.update(Number(id), {
|
|
key,
|
|
value,
|
|
description,
|
|
});
|
|
|
|
if (!variable) {
|
|
return response.status(404).json({
|
|
success: false,
|
|
error: "Variable not found",
|
|
});
|
|
}
|
|
|
|
response.status(200).json({
|
|
success: true,
|
|
variable,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating system prompt variable:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: `Failed to update system prompt variable: ${error.message}`,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete(
|
|
"/system/prompt-variables/:id",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { id } = request.params;
|
|
const success = await SystemPromptVariables.delete(Number(id));
|
|
|
|
if (!success) {
|
|
return response.status(404).json({
|
|
success: false,
|
|
error: "System prompt variable not found or could not be deleted",
|
|
});
|
|
}
|
|
|
|
response.status(200).json({
|
|
success: true,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error deleting system prompt variable:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: `Failed to delete system prompt variable: ${error.message}`,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/validate-sql-connection",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
const { engine, connectionString } = reqBody(request);
|
|
try {
|
|
if (!engine || !connectionString) {
|
|
return response.status(400).json({
|
|
success: false,
|
|
error: "Both engine and connection details are required.",
|
|
});
|
|
}
|
|
|
|
const {
|
|
validateConnection,
|
|
} = require("../utils/agents/aibitat/plugins/sql-agent/SQLConnectors");
|
|
const result = await validateConnection(engine, { connectionString });
|
|
|
|
if (!result.success) {
|
|
return response.status(200).json({
|
|
success: false,
|
|
error: `Unable to connect to ${engine}. Please verify your connection details.`,
|
|
});
|
|
}
|
|
|
|
response.status(200).json(result);
|
|
} catch (error) {
|
|
console.error("SQL validation error:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
error: `Unable to connect to ${engine}. Please verify your connection details.`,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
module.exports = { systemEndpoints };
|