Compare commits

...
94 Commits
Author SHA1 Message Date
Tim Baek b8112d72b9 Merge pull request #21507 from open-webui/dev
refac
2026-02-17 01:25:39 -06:00
Timothy Jaeryang Baek 71ccedd2bf refac 2026-02-17 01:25:18 -06:00
Tim Baek 4225791313 Merge pull request #21458 from open-webui/dev
0.8.3
2026-02-17 01:19:39 -06:00
Timothy Jaeryang Baek e5cd1b479b refac 2026-02-17 01:12:57 -06:00
Timothy Jaeryang Baek 61d44aa773 refac 2026-02-17 01:12:33 -06:00
Timothy Jaeryang Baek ef036529b5 chore: format 2026-02-17 01:11:56 -06:00
Timothy Jaeryang Baek e0bdef85ab chore: format 2026-02-17 01:11:48 -06:00
Timothy Jaeryang Baek 2ce935bdb1 refac 2026-02-17 01:10:19 -06:00
Timothy Jaeryang Baek 23b1e2cca4 refac 2026-02-17 01:00:19 -06:00
Timothy Jaeryang Baek 05b8768fb9 refac 2026-02-17 00:48:49 -06:00
Timothy Jaeryang Baek 173d5631ca refac 2026-02-17 00:31:34 -06:00
Timothy Jaeryang Baek 34cd3d79e8 refac 2026-02-16 23:52:32 -06:00
Classic298 aede1b7a08 chore: Changelog updates (#21497)
* changelog: add model edit shortcut entry

* changelog: add Chinese translation updates for 0.8.3

* changelog: fix USER_EMAIL template variable

* changelog: add fix for missing function error handling in models endpoint

* changelog: startup error handling, translation updates

* changelog: PostgreSQL group query fix

* changelog: images, attachments, vision

* changelog: model edit, image handling, user_email, groups, startup

* changelog: image-edit, model-shortcut, general-improvements

* changelog: image edit API background support

* changelog: image API support, function error handling
2026-02-16 18:33:58 -06:00
Timothy Jaeryang Baek 15b893e651 refac 2026-02-16 15:32:28 -06:00
Classic298 4896d30281 chore: changelog (#21474)
* changelog: add model edit shortcut entry

* changelog: add Chinese translation updates for 0.8.3

* changelog: fix USER_EMAIL template variable

* changelog: add fix for missing function error handling in models endpoint

* changelog: startup error handling, translation updates

* changelog: PostgreSQL group query fix
2026-02-16 14:19:33 -06:00
Timothy Jaeryang Baek 9be45f49e4 refac 2026-02-16 14:13:49 -06:00
Timothy Jaeryang Baek f1053d94c7 refac 2026-02-16 14:08:35 -06:00
Timothy Jaeryang Baek 10cfddccd7 refac: styling 2026-02-16 13:39:41 -06:00
Timothy Jaeryang Baek e5e39be90f refac 2026-02-16 13:14:40 -06:00
Timothy Jaeryang Baek 337109e99c refac 2026-02-16 13:10:01 -06:00
joaoback 8adf2b33b4 i18n: (pt-BR): add translations for newly added UI items + consistency pass (#21472)
i18n: (pt-BR): add translations for newly added UI items + consistency pass
2026-02-16 13:08:38 -06:00
Classic298 1984ce42aa Update Chat.svelte (#21479) 2026-02-16 13:03:22 -06:00
Classic298andClaude 656de56a3e fix: gracefully handle missing functions when loading models (#21476)
When models reference functions (via filterIds/actionIds) that no longer
exist in the database, the /api/models endpoint crashes with a 500 error,
preventing the UI from loading chats entirely. This can happen after
upgrades when built-in functions are removed or when user-created
functions are deleted while still referenced by models.

Instead of raising an exception, log at INFO level and skip the missing
function so the rest of the models load successfully.

Fixes #21464

https://claude.ai/code/session_015JRM7m2bNeZPBBmV2Gv4Mj

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 13:02:54 -06:00
Shirasawa 0b2abe6cb8 I18n: improve Chinese translation (#21460)
* i18n: improve zh-CN translation

* i18n: improve zh-TW translation
2026-02-16 02:17:52 -06:00
Timothy Jaeryang Baek f364b2d205 refac 2026-02-16 02:05:19 -06:00
Timothy Jaeryang Baek 519ff40cb6 refac 2026-02-16 01:56:43 -06:00
Tim Baek 7c7fe44328 Merge pull request #21443 from open-webui/dev
0.8.2
2026-02-16 01:37:13 -06:00
Algorithm5838 15b5f97f89 fix: prevent scroll jump when editing large messages (#21402)
Save and restore scroll positions around textarea auto-resize to avoid
layout shifts. Use preventScroll on focus.
2026-02-16 01:33:51 -06:00
Shamil ca0983f76b i18n: Add missing Russian (ru-RU) translations (#21453)
* i18n: add missing Russian (ru-RU) translations

* i18n: add translations for API Keys and Asc keys
2026-02-16 01:27:23 -06:00
Timothy Jaeryang Baek ef04a704ce chore: format 2026-02-16 01:26:29 -06:00
Timothy Jaeryang Baek 5fda814669 doc: changelog 2026-02-16 01:25:40 -06:00
Timothy Jaeryang Baek c26e8110af enh: renderMarkdownInPreviews 2026-02-16 01:23:56 -06:00
Timothy Jaeryang Baek 9e85055b8b doc: changelog 2026-02-16 00:55:23 -06:00
Timothy Jaeryang Baek 6d17de6c67 chore: bump 2026-02-16 00:53:36 -06:00
Timothy Jaeryang Baek f4e99c80f6 refac: "tool_calls" finish reason support 2026-02-16 00:53:01 -06:00
Timothy Jaeryang Baek 09dc28df1e chore: format 2026-02-16 00:43:32 -06:00
Timothy Jaeryang Baek c748c3ede7 refac 2026-02-16 00:41:36 -06:00
Timothy Jaeryang Baek 33308022f0 refac 2026-02-15 23:57:40 -06:00
Timothy Jaeryang Baek f96e8f04fc refac: styling 2026-02-15 23:50:34 -06:00
Timothy Jaeryang Baek 38ae91ae23 refac 2026-02-15 23:35:40 -06:00
Timothy Jaeryang Baek 88401e91c7 refac 2026-02-15 23:28:47 -06:00
Timothy Jaeryang Baek 8c5cfa530d refac: styling 2026-02-15 22:55:50 -06:00
Timothy Jaeryang Baek d215e46315 refac: styling 2026-02-15 22:49:56 -06:00
Classic298 e10e7d056e chore: changelog (#21424)
* changelog: oauth session error handling

* changelog: task model selector validation fix

* changelog: skill content handling

* changelog: add Spanish and German translations

* changelog: move skill content to Added section

* changelog: tool call, validation fixes

* changelog: tool call message preservation links

* changelog: update per-model tool toggles entry with per-conversation chat toggle feature

* changelog: tool-server, startup, initialization

* changelog: remove empty Changed section

* changelog: fix duplicate Fixed section

* changelog: revert old entry, add new entry to 0.8.2

* changelog: move built-in tool toggles to Added section

* changelog: update date to 2026-02-16

* changelog: resource handle cleanup, file descriptors, leak fix

* changelog: image preview, file modal, modal enhancement
2026-02-15 19:33:20 -06:00
Timothy Jaeryang Baek 7a7d902238 refac 2026-02-15 19:32:22 -06:00
Varun Chawla 24179cde2f fix: preserve trailing slash in MCP server URLs (#21212)
Stop trimming trailing slashes from MCP server URLs on save. Some MCP
servers (e.g. Bitrix24) require a trailing slash; removing it triggers a
301 redirect that drops Authorization headers, resulting in 400 errors.

The trailing-slash trim is now skipped when the connection type is 'mcp',
while OpenAPI connections continue to have it stripped as before.

Fixes open-webui#21179
2026-02-15 19:09:41 -06:00
Timothy Jaeryang Baek 3ae4c618e1 refac 2026-02-15 19:07:53 -06:00
Timothy Jaeryang Baek 4a0d893995 refac 2026-02-15 19:03:08 -06:00
Timothy Jaeryang Baek b780d5c556 refac 2026-02-15 18:41:16 -06:00
Timothy Jaeryang BaekandDario Ruellan 911eecac85 fix: disabled mcp display issue
Co-Authored-By: Dario Ruellan <6965667+druellan@users.noreply.github.com>
2026-02-15 18:10:18 -06:00
Timothy Jaeryang Baek 319d3e8856 refac 2026-02-15 17:55:59 -06:00
Timothy Jaeryang Baek e1b3e7252c enh: preview image in file modal 2026-02-15 17:53:20 -06:00
Timothy Jaeryang BaekandSpootyMcSpoot f20cc6d7e6 refac
Co-Authored-By: SpootyMcSpoot <6732450+spootymcspoot@users.noreply.github.com>
2026-02-15 17:29:55 -06:00
Timothy Jaeryang Baek f1a1e64d2e refac: explicit toggle builtin tools 2026-02-15 17:20:49 -06:00
Classic298 58e923fe00 Update translation.json (#21441) 2026-02-15 16:43:50 -06:00
Timothy Jaeryang Baek f2aca781c8 refac: tool message handling 2026-02-15 16:14:47 -06:00
_00_ ce51c481b8 upd:i18n: Spanish Translation Update v.0.8.0 (#21427)
### Spanish Translation Update v.0.8.0

- Added new strings
2026-02-15 16:05:45 -06:00
Timothy Jaeryang Baek 9a2595f070 fix: task models issue 2026-02-14 22:26:03 -06:00
Timothy Jaeryang Baek 393c0071dc refac: manual skill invocation 2026-02-14 19:22:17 -06:00
Timothy Jaeryang Baek 7e224e4a53 refac 2026-02-13 18:26:03 -06:00
Tim Baek 883f1dda0f Merge pull request #21346 from open-webui/dev
0.8.1
2026-02-13 18:04:11 -06:00
Classic298 12bad452fa chore: Changelog updates (#21382)
* Add v0.8.1 release section

* changelog: knowledge menu layout fix

* changelog: knowledge tooltip z-index fix

* changelog: sync modal community sharing fix

* changelog: postgresql distinct ordering fix

* changelog: security fix public sharing bypass

* changelog: add issue ref to postgresql fix

* changelog: fix postgresql skills json compatibility

* changelog: apply new format style to 0.8.1 entries

* changelog: web search result count fix

* changelog: metadata, document, crash fix

* changelog: add channel user active status performance entry

* changelog: add model and prompt list optimization entry

* changelog: batch access control queries, channel status, model list optimization

* changelog: user list, performance, deferred loading

* Update CHANGELOG.md for 0.8.1

* Add emoji variation to Added section

* Remove empty Changed section and finalize 0.8.1 changelog

* changelog: arm, torch compatibility fix

* changelog: update database migration warning format

* changelog: ollama cloud, model naming fix

* Add SCIM externalId entry and database migration warning to 0.8.1

* Fix: move web search to Added, restore 0.8.0 headers

* Fix: SCIM above translations, 0.8.0 restored

* Remove 0.8.1 migration warning, keep 0.8.0 original

* changelog: direct model access control fix

* changelog: add commit link to direct model access control fix

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update CHANGELOG.md

* changelog: sqlite, cascade delete, database fix

* changelog: responses, api, model-routing

* Add PR and issue links to SCIM externalId changelog entry

* Add commit link to Responses API entry, remove PR link from translation entry

* changelog: reasoning traces, performance, browser

* changelog: password, validation, regex

* Update CHANGELOG.md
2026-02-13 17:46:52 -06:00
Timothy Jaeryang Baek 5de60dc922 refac 2026-02-13 17:44:52 -06:00
Taylor Wilsdon a30b106ea3 fix issues/21399 (#21400) 2026-02-13 17:42:34 -06:00
Timothy Jaeryang Baek d33ad462aa refac 2026-02-13 17:38:57 -06:00
Timothy Jaeryang Baek 3b61562c82 refac 2026-02-13 17:26:54 -06:00
Timothy Jaeryang Baek e5d88be4f3 doc: changelog 2026-02-13 15:08:20 -06:00
Timothy Jaeryang Baek b36f8d9314 chore: format 2026-02-13 15:00:47 -06:00
Timothy Jaeryang Baek 626d236d13 chore: format 2026-02-13 15:00:39 -06:00
Timothy Jaeryang Baek 79ecbfc757 refac 2026-02-13 14:59:20 -06:00
Timothy Jaeryang Baek a9b8677cc0 refac 2026-02-13 14:59:05 -06:00
Classic298 0f3f68b0c4 enh (#21362) 2026-02-13 14:56:53 -06:00
Timothy Jaeryang BaekandJuan Calderon-Perez abc9b63093 refac
Co-Authored-By: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
2026-02-13 14:55:13 -06:00
Timothy Jaeryang Baek 64fa26bd28 refac 2026-02-13 14:48:10 -06:00
Timothy Jaeryang Baek 2487c84f1f refac: styling 2026-02-13 14:47:00 -06:00
Timothy Jaeryang Baek 163211a367 refac: styling 2026-02-13 14:43:06 -06:00
Timothy Jaeryang Baek f027a01ab2 fix: direct model access control 2026-02-13 14:39:01 -06:00
Classic298 370a677a38 fix: pin torch to prevent startup errors on ARM devices (#21385)
* fix: rpi

* Update requirements-min.txt

* Update requirements.txt

* Update pyproject.toml
2026-02-13 14:28:23 -06:00
Timothy Jaeryang Baek d1d1efe212 refac: scim 2026-02-13 14:27:11 -06:00
Timothy Jaeryang Baek b7549d2f6c refac: defer profile 2026-02-13 14:08:07 -06:00
Timothy Jaeryang Baek 589c4e64c1 refac 2026-02-13 13:56:29 -06:00
Timothy Jaeryang Baek 20de5a87da refac 2026-02-13 13:43:43 -06:00
Timothy Jaeryang Baek ca6b18ab5c refac: is_user_active 2026-02-13 13:40:59 -06:00
Classic298 97a3b1528d Update utils.py (#21105) 2026-02-13 13:37:12 -06:00
Classic298 d01b1d4880 enh: apply admin default to builtin web search (#21373) 2026-02-13 13:32:48 -06:00
Timothy Jaeryang Baek df6e38039f refac 2026-02-13 13:29:22 -06:00
Timothy Jaeryang Baek b4c3f54f96 fix: skills postgres issue 2026-02-13 11:24:08 -06:00
Classic298 73776d54b8 fix: enforce public sharing permission checks across all resource types (#21358)
The sharePublic prop in editor components (Knowledge, Tools, Skills,
Prompts, Models) incorrectly included an "|| edit" / "|| write_access"
condition, allowing users with write access to see and use the "Public"
sharing option regardless of their actual public sharing permission.
Additionally, all backend access/update endpoints only verified write
authorization but did not check the corresponding sharing.public_*
permission, allowing direct API calls to bypass frontend restrictions
entirely.
Frontend: removed the edit/write_access bypass from sharePublic in all
five editor components so visibility is gated solely by the user's
sharing.public_* permission or admin role.
Backend: added has_public_read_access_grant checks to the access/update
endpoints in knowledge.py, tools.py, prompts.py, skills.py, models.py,
and notes.py. Public grants are silently stripped when the user lacks
the corresponding permission.
Fixes #21356
2026-02-13 11:22:32 -06:00
Timothy Jaeryang BaekandEntropyYue 7bda6bf767 fix: PostgreSQL cannot use get_chat_ids_by_model_id
Co-Authored-By: EntropyYue <164553692+EntropyYue@users.noreply.github.com>
2026-02-13 11:20:26 -06:00
G30 ddcec9842f fix: ensure sync modal only triggers if community sharing is enabled (#21376) 2026-02-13 11:18:58 -06:00
G30 4d5b7b3014 fix: resolve knowledge tooltip z-index issue in model edit page (#21375) 2026-02-13 11:18:35 -06:00
G30 9886ebb97f fix: resolve knowledge collection indentation/truncation issue by correcting flex layout (#21374) 2026-02-13 11:18:14 -06:00
Timothy Jaeryang Baek 0b05b2fc7e refac 2026-02-13 00:44:01 -06:00
joaoback 49e7eade15 Merge pull request #21345 from joaoback/patch-25
i18n: Update translation.json (pt-BR)
2026-02-12 19:42:52 -06:00
132 changed files with 3184 additions and 1343 deletions
+77
View File
@@ -5,6 +5,83 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.3] - 2026-02-17
### Added
- ✏️ **Model edit shortcut.** Users can now edit models directly from the model selector dropdown menu, making it faster to modify model settings without navigating to separate admin or workspace pages. [Commit](https://github.com/open-webui/open-webui/commit/519ff40cb69cdc1d215cee369e9db70ff7438153)
- 🎨 **Image edit API background support.** The image edit API now supports the background parameter for OpenAI's gpt-image-1 model, enabling background transparency control ("transparent", "opaque", "auto") when the feature is exposed in the UI. [#21459](https://github.com/open-webui/open-webui/pull/21459)
- ⚡ **Faster model filtering.** Model access control filtering no longer makes a redundant database query to re-fetch model info that is already available in memory, reducing latency when loading model lists for non-admin users. [Commit](https://github.com/open-webui/open-webui/commit/34cd3d79e8688f589e3dd2f03415f8a8f9a13115)
- 🔧 **Tool call display improvements.** Tool call results now display arguments in a cleaner key-value format instead of raw JSON, with a responsive layout that shows only the tool name on narrow screens and the full label on wider screens, preventing text wrapping to multiple lines. [Commit](https://github.com/open-webui/open-webui/commit/2ce935bdb10d2b26b230cd54cb649f5c667ed96a)
- 🔄 **General improvements.** Various improvements were implemented across the application to enhance performance, stability, and security.
- 🌐 Translations for Portuguese (Brazil), Simplified Chinese, and Traditional Chinese were enhanced and expanded.
### Fixed
- 📧 **USER_EMAIL variable fix.** The {{USER_EMAIL}} template variable now correctly returns the user's email address instead of "Unknown" in prompts. [#21479](https://github.com/open-webui/open-webui/pull/21479), [#21465](https://github.com/open-webui/open-webui/issues/21465)
- 🖼️ **Image and file attachment handling fixes.** Uploaded images are now correctly sent to vision-enabled models, and file attachments now work even when no user text is entered alongside a system prompt. This fixes two issues where the backend was not properly processing file attachments: images weren't converted to the expected format for API requests, and file context was dropped when the user sent only a file without accompanying text. [Commit](https://github.com/open-webui/open-webui/commit/f1053d94c7ef7b8b78682dd73586b65a84d202a1), [#21477](https://github.com/open-webui/open-webui/issues/21477), [#21457](https://github.com/open-webui/open-webui/issues/21457)
- 🛡️ **Missing function error handling.** Models that reference deleted functions no longer cause the entire /api/models endpoint to crash; instead, the missing functions are skipped and logged, allowing the rest of the models to load successfully. [#21476](https://github.com/open-webui/open-webui/pull/21476), [#21464](https://github.com/open-webui/open-webui/issues/21464)
- 🚀 **Startup model pre-fetch error handling.** If model pre-fetching fails during app startup, the application now logs a warning and continues instead of crashing entirely. [Commit](https://github.com/open-webui/open-webui/commit/337109e99ce390f55a9085d0a301853637923779)
- ⚙️ **Function module loading error handling.** Function modules that fail to load during startup or model processing are now caught and logged, preventing crashes when models reference functions with loading errors. [Commit](https://github.com/open-webui/open-webui/commit/15b893e651de71b033408e1b713e0b51f6829ab8)
- 🗄️ **PostgreSQL group query fix.** The '/api/v1/groups/' endpoint no longer fails with a GROUP BY error when using PostgreSQL; member counts are now calculated using correlated subqueries for better database compatibility. [#21458](https://github.com/open-webui/open-webui/pull/21458), [#21467](https://github.com/open-webui/open-webui/issues/21467)
## [0.8.2] - 2026-02-16
### Added
- 🧠 **Skill content handling.** User-selected skills now have their full content injected into the chat, while model-attached skills only display name and description in the available skills list. This allows users to override skill behavior while model-attached skills remain flexible. [Commit](https://github.com/open-webui/open-webui/commit/393c0071dc612c5ac982fb37dfc0288cb9911439)
- ⚙️ **Chat toggles now control built-in tools.** Users can now disable web search, image generation, and code execution on a per-conversation basis, even when those tools are enabled as builtin tools on the model. [#20641](https://github.com/open-webui/open-webui/issues/20641), [#21318](https://github.com/open-webui/open-webui/discussions/21318), [Commit](https://github.com/open-webui/open-webui/commit/c46ef3b63bcc1e2e9adbdd18fab82c4bbe33ff6c), [Commit](https://github.com/open-webui/open-webui/commit/f1a1e64d2e9ad953b2bc2a9543e9a308b7c669c8)
- 🖼️ **Image preview in file modal.** Images uploaded to chats can now be previewed directly in the file management modal, making it easier to identify and manage image files. [#21413](https://github.com/open-webui/open-webui/issues/21413), [Commit](https://github.com/open-webui/open-webui/commit/e1b3e7252c1896c04d498547908f0fce111434e1)
- 🏷️ **Batch tag operations.** Tag creation, deletion, and orphan cleanup for chats now use batch database queries instead of per-tag loops, significantly reducing database round trips when updating, archiving, or deleting chats with multiple tags. [Commit](https://github.com/open-webui/open-webui/commit/c748c3ede)
- 💨 **Faster group list loading.** Group lists and search results now load with a single database query that joins member counts, replacing the previous pattern of fetching groups first and then counting members in a separate batch query. [Commit](https://github.com/open-webui/open-webui/commit/33308022f)
- 🔐 **Skills sharing permissions.** Administrators can now control skills sharing and public sharing permissions per-group, matching the existing capabilities for tools, knowledge, and prompts. [Commit](https://github.com/open-webui/open-webui/commit/88401e91c)
- ⚡ **Long content truncation in preview modals.** Citation and file content modals now truncate markdown-rendered content at 10,000 characters with a "Show all" expansion button, preventing UI jank when previewing very large documents.
- 🌐 **Translation updates.** Translations for Spanish and German were enhanced and expanded.
### Fixed
- 🔐 **OAuth session error handling.** Corrupted OAuth sessions are now gracefully handled and automatically cleaned up instead of causing errors. [Commit](https://github.com/open-webui/open-webui/commit/7e224e4a536b07ec008613f06592e34050e7067c)
- 🐛 **Task model selector validation.** The task model selector in admin settings now correctly accepts models based on the new access grants system instead of rejecting all models with an incorrect error. [Commit](https://github.com/open-webui/open-webui/commit/9a2595f0706d0c9d809ae7746001cf799f98db1d)
- 🔗 **Tool call message preservation.** Models no longer hallucinate tool outputs in multi-turn conversations because tool call history is now properly preserved instead of being merged into assistant messages. [#21098](https://github.com/open-webui/open-webui/discussions/21098), [#20600](https://github.com/open-webui/open-webui/issues/20600), [Commit](https://github.com/open-webui/open-webui/commit/f2aca781c87244cffc130aa2722e700c19a81d66)
- 🔧 **Tool server startup initialization.** External tool servers configured via the "TOOL_SERVER_CONNECTIONS" environment variable now initialize automatically on startup, eliminating the need to manually visit the Admin Panel and save for tools to become available. This enables proper GitOps and containerized deployments. [#18140](https://github.com/open-webui/open-webui/issues/18140), [#20914](https://github.com/open-webui/open-webui/pull/20914), [Commit](https://github.com/open-webui/open-webui/commit/f20cc6d7e6da493eb75ca1618f5cbd068fa57684)
- ♻️ **Resource handle cleanup.** File handles are now properly closed during audio transcription and pipeline uploads, preventing resource leaks that could cause system instability over time. [#21411](https://github.com/open-webui/open-webui/issues/21411)
- ⌨️ **Strikethrough shortcut conflict fix.** Pressing Ctrl+Shift+S to toggle the sidebar no longer causes text to become struck through in the chat input, by disabling the TipTap Strike extension's default keyboard shortcut when rich text mode is off. [Commit](https://github.com/open-webui/open-webui/commit/38ae91ae2)
- 🔧 **Tool call finish_reason fix.** API responses now correctly set finish_reason to "tool_calls" instead of "stop" when tool calls are present, fixing an issue where external API clients (such as OpenCode) would halt prematurely after tool execution when routing Ollama models through the Open WebUI API. [#20896](https://github.com/open-webui/open-webui/issues/20896)
## [0.8.1] - 2026-02-14
### Added
- 🚀 **Channel user active status.** Checking user active status in channels is now faster thanks to optimized database queries. [Commit](https://github.com/open-webui/open-webui/commit/ca6b18ab5cb94153a9dae233f975d36bf6b19b76)
- 🔗 **Responses API endpoint with model routing.** The OpenAI API proxy now supports a /responses endpoint that routes requests to the correct backend based on the model field in the request, instead of always using the first configured endpoint. This enables support for backends like vLLM that provide /skills and /v1/responses endpoints. [Commit](https://github.com/open-webui/open-webui/commit/abc9b63093d65f4d74342db85b7d5df1809aa0f0), [Commit](https://github.com/open-webui/open-webui/commit/79ecbfc757f0642740d0e44fab98263d84295490)
- ⚡ **Model and prompt list optimization.** Improved performance when loading models and prompts by pre-fetching user group IDs once instead of making multiple database queries. [Commit](https://github.com/open-webui/open-webui/commit/20de5a87da0c12e4052b50887a42ddd7228c5ef5)
- 🗄️ **Batch access control queries.** Improved performance when loading models, prompts, and knowledge bases by replacing multiple individual access checks with single batch queries, significantly reducing database load for large deployments. [Commit](https://github.com/open-webui/open-webui/commit/589c4e64c1b7bb7a7a5abc20382b92fb860e28c2)
- 💨 **Faster user list loading.** User lists now load significantly faster by deferring profile image loading; images are fetched separately in parallel by the browser, improving caching and reducing database load. [Commit](https://github.com/open-webui/open-webui/commit/b7549d2f6ca2843661ec79a5a1e55da9e7553368)
- 🔍 **Web search result count.** The built-in search_web tool now respects the admin-configured "Search Result Count" setting instead of always returning 5 results when using Native Function Calling mode. [#21373](https://github.com/open-webui/open-webui/pull/21373), [#21371](https://github.com/open-webui/open-webui/issues/21371)
- 🔐 **SCIM externalId support.** SCIM-enabled deployments can now store and manage externalId for user provisioning, enabling better integration with identity providers like Microsoft Entra ID and Okta. [#21099](https://github.com/open-webui/open-webui/pull/21099), [#21280](https://github.com/open-webui/open-webui/issues/21280), [Commit](https://github.com/open-webui/open-webui/commit/d1d1efe212b16e0052359991d67fd813125077e8)
- 🌐 **Translation updates.** Portuguese (Brazil) translations were updated.
### Fixed
- 🛡️ **Public sharing security fix.** Fixed a security issue where users with write access could see the Public sharing option regardless of their actual public sharing permission, and direct API calls could bypass frontend sharing restrictions. [#21358](https://github.com/open-webui/open-webui/pull/21358), [#21356](https://github.com/open-webui/open-webui/issues/21356)
- 🔒 **Direct model access control fix.** Model access control changes now persist correctly for direct Ollama and OpenAI models that don't have database entries, and error messages display properly instead of showing "[object Object]". [Commit](https://github.com/open-webui/open-webui/commit/f027a01ab2ff3b6175af3dd13a4478c265c0544a), [#21377](https://github.com/open-webui/open-webui/issues/21377)
- 💭 **Reasoning trace rendering performance.** Reasoning traces from models now render properly without being split into many fragments, preventing browser slowdowns during streaming responses. [#21348](https://github.com/open-webui/open-webui/issues/21348), [Commit](https://github.com/open-webui/open-webui/commit/3b61562c82448cf83710d8b6ed29b797991aa83a)
- 🖥️ **ARM device compatibility fix.** Fixed an issue where upgrading to 0.8.0 would fail to start on ARM devices (like Raspberry Pi 4) due to torch 2.10.0 causing SIGILL errors; now pinned to torch<=2.9.1. [#21385](https://github.com/open-webui/open-webui/pull/21385), [#21349](https://github.com/open-webui/open-webui/issues/21349)
- 🗄️ **Skills PostgreSQL compatibility fix.** Fixed a PostgreSQL compatibility issue where creating or listing skills would fail with a TypeError, while SQLite worked correctly. [#21372](https://github.com/open-webui/open-webui/pull/21372), [Commit](https://github.com/open-webui/open-webui/commit/b4c3f54f9648c4232a0fd6557703ffa66fcf4caa), [#21365](https://github.com/open-webui/open-webui/issues/21365)
- 🗄️ **PostgreSQL analytics query fix.** Fixed an issue where retrieving chat IDs by model ID would fail on PostgreSQL due to incompatible DISTINCT ordering, while SQLite worked correctly. [#21347](https://github.com/open-webui/open-webui/issues/21347), [Commit](https://github.com/open-webui/open-webui/commit/7bda6bf767d5d5c4dc1111465096a88e10b5030e)
- 🗃️ **SQLite cascade delete fix.** Deleting chats now properly removes all associated messages in SQLite, matching PostgreSQL behavior and preventing orphaned data. [#21362](https://github.com/open-webui/open-webui/pull/21362)
- ☁️ **Ollama Cloud model naming fix.** Fixed an issue where using Ollama Cloud models would fail with "Model not found" errors because ":latest" was incorrectly appended to model names. [#21386](https://github.com/open-webui/open-webui/issues/21386)
- 🛠️ **Knowledge selector tooltip z-index.** Fixed an issue where tooltips in the "Select Knowledge" dropdown were hidden behind the menu, making it difficult to read knowledge item names and descriptions. [#21375](https://github.com/open-webui/open-webui/pull/21375)
- 🎯 **Model selector scroll position.** The model selector dropdown now correctly scrolls to and centers the currently selected model when opened, and resets scroll position when reopened. [Commit](https://github.com/open-webui/open-webui/commit/0b05b2fc7ed4c38af158707438ff404d1beb7c91)
- 🐛 **Sync modal unexpected appearance.** Fixed an issue where the Sync Modal would appear unexpectedly after enabling the "Community Sharing" feature if the user had previously visited the app with the sync parameter. [#21376](https://github.com/open-webui/open-webui/pull/21376)
- 🎨 **Knowledge collection layout fix.** Fixed a layout issue in the Knowledge integration menu where long collection names caused indentation artifacts and now properly truncate with ellipsis. [#21374](https://github.com/open-webui/open-webui/pull/21374)
- 📝 **Metadata processing crash fix.** Fixed a latent bug where processing document metadata containing certain keys (content, pages, tables, paragraphs, sections, figures) would cause a RuntimeError due to dictionary mutation during iteration. [#21105](https://github.com/open-webui/open-webui/pull/21105)
- 🔑 **Password validation regex fix.** Fixed the password validation regex by adding the raw string prefix, ensuring escape sequences like \d and \w are interpreted correctly. [#21400](https://github.com/open-webui/open-webui/pull/21400), [#21399](https://github.com/open-webui/open-webui/issues/21399)
### Changed
- ⚠️ **Database Migrations:** This release includes database schema changes; we strongly recommend backing up your database and all associated data before upgrading in production environments. If you are running a multi-worker, multi-server, or load-balanced deployment, all instances must be updated simultaneously, rolling updates are not supported and will cause application failures due to schema incompatibility.
## [0.8.0] - 2026-02-12
### Added
+3 -2
View File
@@ -137,7 +137,8 @@ COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
RUN pip3 install --no-cache-dir uv && \
if [ "$USE_CUDA" = "true" ]; then \
# If you use CUDA the whisper and embedding model will be downloaded on first use
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
# fix: pin torch<=2.9.1 - torch 2.10.0 aarch64 wheels cause SIGILL on ARM devices (RPi 4 Cortex-A72) #21349
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
uv pip install --system -r requirements.txt --no-cache-dir && \
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
@@ -145,7 +146,7 @@ RUN pip3 install --no-cache-dir uv && \
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
python -c "import nltk; nltk.download('punkt_tab')"; \
else \
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
uv pip install --system -r requirements.txt --no-cache-dir && \
if [ "$USE_SLIM" != "true" ]; then \
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
+14
View File
@@ -1385,6 +1385,18 @@ USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING = (
== "true"
)
USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING = (
os.environ.get("USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING", "False").lower()
== "true"
)
USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING = (
os.environ.get(
"USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING", "False"
).lower()
== "true"
)
USER_PERMISSIONS_NOTES_ALLOW_SHARING = (
os.environ.get("USER_PERMISSIONS_NOTES_ALLOW_SHARING", "False").lower() == "true"
@@ -1543,6 +1555,8 @@ DEFAULT_USER_PERMISSIONS = {
"public_prompts": USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING,
"tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_SHARING,
"public_tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING,
"skills": USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_SHARING,
"public_skills": USER_PERMISSIONS_WORKSPACE_SKILLS_ALLOW_PUBLIC_SHARING,
"notes": USER_PERMISSIONS_NOTES_ALLOW_SHARING,
"public_notes": USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING,
},
+9 -1
View File
@@ -478,7 +478,7 @@ ENABLE_PASSWORD_VALIDATION = (
)
PASSWORD_VALIDATION_REGEX_PATTERN = os.environ.get(
"PASSWORD_VALIDATION_REGEX_PATTERN",
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$",
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$",
)
@@ -572,6 +572,14 @@ ENABLE_SCIM = (
== "true"
)
SCIM_TOKEN = os.environ.get("SCIM_TOKEN", "")
SCIM_AUTH_PROVIDER = os.environ.get("SCIM_AUTH_PROVIDER", "")
if ENABLE_SCIM and not SCIM_AUTH_PROVIDER:
log.warning(
"SCIM is enabled but SCIM_AUTH_PROVIDER is not set. "
"Set SCIM_AUTH_PROVIDER to the OAuth provider name (e.g. 'microsoft', 'oidc') "
"to enable externalId storage."
)
####################################
# LICENSE_KEY
+36 -6
View File
@@ -64,6 +64,7 @@ from open_webui.socket.main import (
MODELS,
app as socket_app,
periodic_usage_pool_cleanup,
periodic_session_pool_cleanup,
get_event_emitter,
get_models_in_use,
)
@@ -517,6 +518,7 @@ from open_webui.utils.middleware import (
process_chat_payload,
process_chat_response,
)
from open_webui.utils.tools import set_tool_servers
from open_webui.utils.auth import (
get_license_data,
@@ -634,11 +636,37 @@ async def lifespan(app: FastAPI):
limiter.total_tokens = THREAD_POOL_SIZE
asyncio.create_task(periodic_usage_pool_cleanup())
asyncio.create_task(periodic_session_pool_cleanup())
if app.state.config.ENABLE_BASE_MODELS_CACHE:
await get_all_models(
Request(
# Creating a mock request object to pass to get_all_models
try:
await get_all_models(
Request(
# Creating a mock request object to pass to get_all_models
{
"type": "http",
"asgi.version": "3.0",
"asgi.spec_version": "2.0",
"method": "GET",
"path": "/internal",
"query_string": b"",
"headers": Headers({}).raw,
"client": ("127.0.0.1", 12345),
"server": ("127.0.0.1", 80),
"scheme": "http",
"app": app,
}
),
None,
)
except Exception as e:
log.warning(f"Failed to pre-fetch models at startup: {e}")
# Pre-fetch tool server specs so the first request doesn't pay the latency cost
if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0:
log.info("Initializing tool servers...")
try:
mock_request = Request(
{
"type": "http",
"asgi.version": "3.0",
@@ -652,9 +680,11 @@ async def lifespan(app: FastAPI):
"scheme": "http",
"app": app,
}
),
None,
)
)
await set_tool_servers(mock_request)
log.info(f"Initialized {len(app.state.TOOL_SERVERS)} tool server(s)")
except Exception as e:
log.warning(f"Failed to initialize tool servers at startup: {e}")
yield
@@ -0,0 +1,26 @@
"""add scim column to user table
Revision ID: b2c3d4e5f6a7
Revises: a1b2c3d4e5f6
Create Date: 2026-02-13 14:19:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "b2c3d4e5f6a7"
down_revision: Union[str, None] = "a1b2c3d4e5f6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column("user", sa.Column("scim", sa.JSON(), nullable=True))
def downgrade() -> None:
op.drop_column("user", "scim")
@@ -515,6 +515,62 @@ class AccessGrantsTable:
)
return exists is not None
def get_accessible_resource_ids(
self,
user_id: str,
resource_type: str,
resource_ids: list[str],
permission: str = "read",
user_group_ids: Optional[set[str]] = None,
db: Optional[Session] = None,
) -> set[str]:
"""
Batch check: return the subset of resource_ids that the user can access.
This replaces calling has_access() in a loop (N+1) with a single query.
"""
if not resource_ids:
return set()
with get_db_context(db) as db:
conditions = [
and_(
AccessGrant.principal_type == "user",
AccessGrant.principal_id == "*",
),
and_(
AccessGrant.principal_type == "user",
AccessGrant.principal_id == user_id,
),
]
if user_group_ids is None:
from open_webui.models.groups import Groups
user_groups = Groups.get_groups_by_member_id(user_id, db=db)
user_group_ids = {group.id for group in user_groups}
if user_group_ids:
conditions.append(
and_(
AccessGrant.principal_type == "group",
AccessGrant.principal_id.in_(user_group_ids),
)
)
rows = (
db.query(AccessGrant.resource_id)
.filter(
AccessGrant.resource_type == resource_type,
AccessGrant.resource_id.in_(resource_ids),
AccessGrant.permission == permission,
or_(*conditions),
)
.distinct()
.all()
)
return {row[0] for row in rows}
def get_users_with_access(
self,
resource_type: str,
+9 -7
View File
@@ -15,6 +15,7 @@ from sqlalchemy import (
Text,
JSON,
Index,
func,
)
####################
@@ -279,25 +280,26 @@ class ChatMessageTable:
db: Optional[Session] = None,
) -> list[str]:
"""Get distinct chat_ids that used a specific model."""
from sqlalchemy import distinct
with get_db_context(db) as db:
query = db.query(distinct(ChatMessage.chat_id)).filter(
ChatMessage.model_id == model_id
)
query = db.query(
ChatMessage.chat_id,
func.max(ChatMessage.created_at).label("last_message_at"),
).filter(ChatMessage.model_id == model_id)
if start_date:
query = query.filter(ChatMessage.created_at >= start_date)
if end_date:
query = query.filter(ChatMessage.created_at <= end_date)
# Order by most recent message in each chat
# Group by chat_id and order by most recent message in each chat
chat_ids = (
query.order_by(ChatMessage.created_at.desc())
query.group_by(ChatMessage.chat_id)
.order_by(func.max(ChatMessage.created_at).desc())
.offset(skip)
.limit(limit)
.all()
)
return [chat_id for (chat_id,) in chat_ids]
return [chat_id for chat_id, _ in chat_ids]
def delete_messages_by_chat_id(
self, chat_id: str, db: Optional[Session] = None
+82 -34
View File
@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
from open_webui.models.tags import TagModel, Tag, Tags
from open_webui.models.folders import Folders
from open_webui.models.chat_messages import ChatMessages
from open_webui.models.chat_messages import ChatMessage, ChatMessages
from open_webui.utils.misc import sanitize_data_for_db, sanitize_text_for_db
from pydantic import BaseModel, ConfigDict
@@ -431,22 +431,29 @@ class ChatTable:
def update_chat_tags_by_id(
self, id: str, tags: list[str], user
) -> Optional[ChatModel]:
chat = self.get_chat_by_id(id)
if chat is None:
return None
with get_db_context() as db:
chat = db.get(Chat, id)
if chat is None:
return None
self.delete_all_tags_by_id_and_user_id(id, user.id)
old_tags = chat.meta.get("tags", [])
new_tags = [t for t in tags if t.replace(" ", "_").lower() != "none"]
new_tag_ids = [t.replace(" ", "_").lower() for t in new_tags]
for tag in chat.meta.get("tags", []):
if self.count_chats_by_tag_name_and_user_id(tag, user.id) == 0:
Tags.delete_tag_by_name_and_user_id(tag, user.id)
# Single meta update
chat.meta = {**chat.meta, "tags": new_tag_ids}
db.commit()
db.refresh(chat)
for tag_name in tags:
if tag_name.lower() == "none":
continue
# Batch-create any missing tag rows
Tags.ensure_tags_exist(new_tags, user.id, db=db)
self.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, tag_name)
return self.get_chat_by_id(id)
# Clean up orphaned old tags in one query
removed = set(old_tags) - set(new_tag_ids)
if removed:
self.delete_orphan_tags_for_user(list(removed), user.id, db=db)
return ChatModel.model_validate(chat)
def get_chat_title_by_id(self, id: str) -> Optional[str]:
chat = self.get_chat_by_id(id)
@@ -621,6 +628,13 @@ class ChatTable:
) -> bool:
try:
with get_db_context(db) as db:
# Use subquery to delete chat_messages for shared chats
shared_chat_id_subquery = (
db.query(Chat.id).filter_by(user_id=f"shared-{chat_id}").subquery()
)
db.query(ChatMessage).filter(
ChatMessage.chat_id.in_(shared_chat_id_subquery)
).delete(synchronize_session=False)
db.query(Chat).filter_by(user_id=f"shared-{chat_id}").delete()
db.commit()
@@ -1260,8 +1274,8 @@ class ChatTable:
) -> list[TagModel]:
with get_db_context(db) as db:
chat = db.get(Chat, id)
tags = chat.meta.get("tags", [])
return [Tags.get_tag_by_name_and_user_id(tag, user_id) for tag in tags]
tag_ids = chat.meta.get("tags", [])
return Tags.get_tags_by_ids_and_user_id(tag_ids, user_id, db=db)
def get_chat_list_by_user_id_and_tag_name(
self,
@@ -1302,20 +1316,16 @@ class ChatTable:
def add_chat_tag_by_id_and_user_id_and_tag_name(
self, id: str, user_id: str, tag_name: str, db: Optional[Session] = None
) -> Optional[ChatModel]:
tag = Tags.get_tag_by_name_and_user_id(tag_name, user_id)
if tag is None:
tag = Tags.insert_new_tag(tag_name, user_id)
tag_id = tag_name.replace(" ", "_").lower()
Tags.ensure_tags_exist([tag_name], user_id, db=db)
try:
with get_db_context(db) as db:
chat = db.get(Chat, id)
tag_id = tag.id
if tag_id not in chat.meta.get("tags", []):
chat.meta = {
**chat.meta,
"tags": list(set(chat.meta.get("tags", []) + [tag_id])),
}
db.commit()
db.refresh(chat)
return ChatModel.model_validate(chat)
@@ -1325,40 +1335,53 @@ class ChatTable:
def count_chats_by_tag_name_and_user_id(
self, tag_name: str, user_id: str, db: Optional[Session] = None
) -> int:
with get_db_context(db) as db: # Assuming `get_db()` returns a session object
with get_db_context(db) as db:
query = db.query(Chat).filter_by(user_id=user_id, archived=False)
# Normalize the tag_name for consistency
tag_id = tag_name.replace(" ", "_").lower()
if db.bind.dialect.name == "sqlite":
# SQLite JSON1 support for querying the tags inside the `meta` JSON field
query = query.filter(
text(
f"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
"EXISTS (SELECT 1 FROM json_each(Chat.meta, '$.tags') WHERE json_each.value = :tag_id)"
)
).params(tag_id=tag_id)
elif db.bind.dialect.name == "postgresql":
# PostgreSQL JSONB support for querying the tags inside the `meta` JSON field
query = query.filter(
text(
"EXISTS (SELECT 1 FROM json_array_elements_text(Chat.meta->'tags') elem WHERE elem = :tag_id)"
)
).params(tag_id=tag_id)
else:
raise NotImplementedError(
f"Unsupported dialect: {db.bind.dialect.name}"
)
# Get the count of matching records
count = query.count()
return query.count()
# Debugging output for inspection
log.info(f"Count of chats for tag '{tag_name}': {count}")
def delete_orphan_tags_for_user(
self,
tag_ids: list[str],
user_id: str,
threshold: int = 0,
db: Optional[Session] = None,
) -> None:
"""Delete tag rows from *tag_ids* that appear in at most *threshold*
non-archived chats for *user_id*. One query to find orphans, one to
delete them.
return count
Use threshold=0 after a tag is already removed from a chat's meta.
Use threshold=1 when the chat itself is about to be deleted (the
referencing chat still exists at query time).
"""
if not tag_ids:
return
with get_db_context(db) as db:
orphans = []
for tag_id in tag_ids:
count = self.count_chats_by_tag_name_and_user_id(tag_id, user_id, db=db)
if count <= threshold:
orphans.append(tag_id)
Tags.delete_tags_by_ids_and_user_id(orphans, user_id, db=db)
def count_chats_by_folder_id_and_user_id(
self, folder_id: str, user_id: str, db: Optional[Session] = None
@@ -1410,6 +1433,7 @@ class ChatTable:
def delete_chat_by_id(self, id: str, db: Optional[Session] = None) -> bool:
try:
with get_db_context(db) as db:
db.query(ChatMessage).filter_by(chat_id=id).delete()
db.query(Chat).filter_by(id=id).delete()
db.commit()
@@ -1422,6 +1446,7 @@ class ChatTable:
) -> bool:
try:
with get_db_context(db) as db:
db.query(ChatMessage).filter_by(chat_id=id).delete()
db.query(Chat).filter_by(id=id, user_id=user_id).delete()
db.commit()
@@ -1436,6 +1461,12 @@ class ChatTable:
with get_db_context(db) as db:
self.delete_shared_chats_by_user_id(user_id, db=db)
chat_id_subquery = (
db.query(Chat.id).filter_by(user_id=user_id).subquery()
)
db.query(ChatMessage).filter(
ChatMessage.chat_id.in_(chat_id_subquery)
).delete(synchronize_session=False)
db.query(Chat).filter_by(user_id=user_id).delete()
db.commit()
@@ -1448,6 +1479,14 @@ class ChatTable:
) -> bool:
try:
with get_db_context(db) as db:
chat_id_subquery = (
db.query(Chat.id)
.filter_by(user_id=user_id, folder_id=folder_id)
.subquery()
)
db.query(ChatMessage).filter(
ChatMessage.chat_id.in_(chat_id_subquery)
).delete(synchronize_session=False)
db.query(Chat).filter_by(user_id=user_id, folder_id=folder_id).delete()
db.commit()
@@ -1481,6 +1520,15 @@ class ChatTable:
chats_by_user = db.query(Chat).filter_by(user_id=user_id).all()
shared_chat_ids = [f"shared-{chat.id}" for chat in chats_by_user]
# Use subquery to delete chat_messages for shared chats
shared_id_subq = (
db.query(Chat.id)
.filter(Chat.user_id.in_(shared_chat_ids))
.subquery()
)
db.query(ChatMessage).filter(
ChatMessage.chat_id.in_(shared_id_subq)
).delete(synchronize_session=False)
db.query(Chat).filter(Chat.user_id.in_(shared_chat_ids)).delete()
db.commit()
+46 -25
View File
@@ -164,7 +164,14 @@ class GroupTable:
def get_groups(self, filter, db: Optional[Session] = None) -> list[GroupResponse]:
with get_db_context(db) as db:
query = db.query(Group)
member_count = (
select(func.count(GroupMember.user_id))
.where(GroupMember.group_id == Group.id)
.correlate(Group)
.scalar_subquery()
.label("member_count")
)
query = db.query(Group, member_count)
if filter:
if "query" in filter:
@@ -179,9 +186,6 @@ class GroupTable:
json_share_lower = func.lower(json_share_str)
if share_value:
# Groups open to anyone: data is null, config.share is null, or share is true
# Use case-insensitive string comparison to handle variations like "True", "TRUE"
# Handle potential JSON boolean to string casting issues by checking for both string 'true' and boolean equivalence if possible,
anyone_can_share = or_(
Group.data.is_(None),
json_share_str.is_(None),
@@ -190,7 +194,6 @@ class GroupTable:
)
if member_id:
# Also include member-only groups where user is a member
member_groups_select = select(GroupMember.group_id).where(
GroupMember.user_id == member_id
)
@@ -211,21 +214,24 @@ class GroupTable:
else:
# Only apply member_id filter when share filter is NOT present
if "member_id" in filter:
query = query.join(
GroupMember, GroupMember.group_id == Group.id
).filter(GroupMember.user_id == filter["member_id"])
query = query.filter(
Group.id.in_(
select(GroupMember.group_id).where(
GroupMember.user_id == filter["member_id"]
)
)
)
results = query.order_by(Group.updated_at.desc()).all()
groups = query.order_by(Group.updated_at.desc()).all()
group_ids = [group.id for group in groups]
member_counts = self.get_group_member_counts_by_ids(group_ids, db=db)
return [
GroupResponse.model_validate(
{
**GroupModel.model_validate(group).model_dump(),
"member_count": member_counts.get(group.id, 0),
"member_count": count or 0,
}
)
for group in groups
for group, count in results
]
def search_groups(
@@ -242,31 +248,46 @@ class GroupTable:
if "query" in filter:
query = query.filter(Group.name.ilike(f"%{filter['query']}%"))
if "member_id" in filter:
query = query.join(
GroupMember, GroupMember.group_id == Group.id
).filter(GroupMember.user_id == filter["member_id"])
query = query.filter(
Group.id.in_(
select(GroupMember.group_id).where(
GroupMember.user_id == filter["member_id"]
)
)
)
if "share" in filter:
# 'share' is stored in data JSON, support both sqlite and postgres
share_value = filter["share"]
print("Filtering by share:", share_value)
query = query.filter(
Group.data.op("->>")("share") == str(share_value)
)
total = query.count()
query = query.order_by(Group.updated_at.desc())
groups = query.offset(skip).limit(limit).all()
group_ids = [group.id for group in groups]
member_counts = self.get_group_member_counts_by_ids(group_ids, db=db)
member_count = (
select(func.count(GroupMember.user_id))
.where(GroupMember.group_id == Group.id)
.correlate(Group)
.scalar_subquery()
.label("member_count")
)
results = (
query.add_columns(member_count)
.order_by(Group.updated_at.desc())
.offset(skip)
.limit(limit)
.all()
)
return {
"items": [
GroupResponse.model_validate(
**GroupModel.model_validate(group).model_dump(),
member_count=member_counts.get(group.id, 0),
{
**GroupModel.model_validate(group).model_dump(),
"member_count": count or 0,
}
)
for group in groups
for group, count in results
],
"total": total,
}
+10 -3
View File
@@ -102,7 +102,7 @@ class OAuthSessionTable:
decrypted = self.fernet.decrypt(token.encode()).decode()
return json.loads(decrypted)
except Exception as e:
log.error(f"Error decrypting tokens: {e}")
log.error(f"Error decrypting tokens: {type(e).__name__}: {e}")
raise
def create_session(
@@ -209,8 +209,15 @@ class OAuthSessionTable:
results = []
for session in sessions:
session.token = self._decrypt_token(session.token)
results.append(OAuthSessionModel.model_validate(session))
try:
session.token = self._decrypt_token(session.token)
results.append(OAuthSessionModel.model_validate(session))
except Exception as e:
log.warning(
f"Skipping OAuth session {session.id} due to decryption failure, deleting corrupted session: {type(e).__name__}: {e}"
)
db.query(OAuthSession).filter_by(id=session.id).delete()
db.commit()
return results
+3 -3
View File
@@ -3,13 +3,13 @@ import time
from typing import Optional
from sqlalchemy.orm import Session
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
from open_webui.internal.db import Base, get_db, get_db_context
from open_webui.models.users import Users, UserResponse
from open_webui.models.groups import Groups
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import BigInteger, Boolean, Column, String, Text, or_
from sqlalchemy import JSON, BigInteger, Boolean, Column, String, Text, or_
log = logging.getLogger(__name__)
@@ -26,7 +26,7 @@ class Skill(Base):
name = Column(Text, unique=True)
description = Column(Text, nullable=True)
content = Column(Text)
meta = Column(JSONField)
meta = Column(JSON)
is_active = Column(Boolean, default=True)
updated_at = Column(BigInteger)
+40
View File
@@ -115,5 +115,45 @@ class TagTable:
log.error(f"delete_tag: {e}")
return False
def delete_tags_by_ids_and_user_id(
self, ids: list[str], user_id: str, db: Optional[Session] = None
) -> bool:
"""Delete all tags whose id is in *ids* for the given user, in one query."""
if not ids:
return True
try:
with get_db_context(db) as db:
db.query(Tag).filter(Tag.id.in_(ids), Tag.user_id == user_id).delete(
synchronize_session=False
)
db.commit()
return True
except Exception as e:
log.error(f"delete_tags_by_ids: {e}")
return False
def ensure_tags_exist(
self, names: list[str], user_id: str, db: Optional[Session] = None
) -> None:
"""Create tag rows for any *names* that don't already exist for *user_id*."""
if not names:
return
ids = [n.replace(" ", "_").lower() for n in names]
with get_db_context(db) as db:
existing = {
t.id
for t in db.query(Tag.id)
.filter(Tag.id.in_(ids), Tag.user_id == user_id)
.all()
}
new_tags = [
Tag(id=tag_id, name=name, user_id=user_id)
for tag_id, name in zip(ids, names)
if tag_id not in existing
]
if new_tags:
db.add_all(new_tags)
db.commit()
Tags = TagTable()
+89 -8
View File
@@ -1,7 +1,7 @@
import time
from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, defer
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
@@ -15,7 +15,7 @@ from open_webui.utils.misc import throttle
from open_webui.utils.validate import validate_profile_image_url
from pydantic import BaseModel, ConfigDict, field_validator
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
from sqlalchemy import (
BigInteger,
JSON,
@@ -28,7 +28,7 @@ from sqlalchemy import (
select,
cast,
)
from sqlalchemy import or_, case
from sqlalchemy import or_, case, func
from sqlalchemy.dialects.postgresql import JSONB
import datetime
@@ -71,6 +71,7 @@ class User(Base):
settings = Column(JSON, nullable=True)
oauth = Column(JSON, nullable=True)
scim = Column(JSON, nullable=True)
last_active_at = Column(BigInteger)
updated_at = Column(BigInteger)
@@ -86,7 +87,7 @@ class UserModel(BaseModel):
name: str
profile_image_url: str
profile_image_url: Optional[str] = None
profile_banner_image_url: Optional[str] = None
bio: Optional[str] = None
@@ -103,6 +104,7 @@ class UserModel(BaseModel):
settings: Optional[UserSettings] = None
oauth: Optional[dict] = None
scim: Optional[dict] = None
last_active_at: int # timestamp in epoch
updated_at: int # timestamp in epoch
@@ -110,6 +112,12 @@ class UserModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
@model_validator(mode="after")
def set_profile_image_url(self):
if not self.profile_image_url:
self.profile_image_url = f"/api/v1/users/{self.id}/profile/image"
return self
class UserStatusModel(UserModel):
is_active: bool = False
@@ -315,8 +323,12 @@ class UsersTable:
) -> Optional[UserModel]:
try:
with get_db_context(db) as db:
user = db.query(User).filter_by(email=email).first()
return UserModel.model_validate(user)
user = (
db.query(User)
.filter(func.lower(User.email) == email.lower())
.first()
)
return UserModel.model_validate(user) if user else None
except Exception:
return None
@@ -341,6 +353,29 @@ class UsersTable:
# You may want to log the exception here
return None
def get_user_by_scim_external_id(
self, provider: str, external_id: str, db: Optional[Session] = None
) -> Optional[UserModel]:
try:
with get_db_context(db) as db: # type: Session
dialect_name = db.bind.dialect.name
query = db.query(User)
if dialect_name == "sqlite":
query = query.filter(
User.scim.contains({provider: {"external_id": external_id}})
)
elif dialect_name == "postgresql":
query = query.filter(
User.scim[provider].cast(JSONB)["external_id"].astext
== external_id
)
user = query.first()
return UserModel.model_validate(user) if user else None
except Exception:
return None
def get_users(
self,
filter: Optional[dict] = None,
@@ -350,7 +385,7 @@ class UsersTable:
) -> dict:
with get_db_context(db) as db:
# Join GroupMember so we can order by group_id when requested
query = db.query(User)
query = db.query(User).options(defer(User.profile_image_url))
if filter:
query_key = filter.get("query")
@@ -485,6 +520,7 @@ class UsersTable:
with get_db_context(db) as db:
users = (
db.query(User)
.options(defer(User.profile_image_url))
.join(GroupMember, User.id == GroupMember.user_id)
.filter(GroupMember.group_id == group_id)
.all()
@@ -495,7 +531,12 @@ class UsersTable:
self, user_ids: list[str], db: Optional[Session] = None
) -> list[UserStatusModel]:
with get_db_context(db) as db:
users = db.query(User).filter(User.id.in_(user_ids)).all()
users = (
db.query(User)
.options(defer(User.profile_image_url))
.filter(User.id.in_(user_ids))
.all()
)
return [UserModel.model_validate(user) for user in users]
def get_num_users(self, db: Optional[Session] = None) -> Optional[int]:
@@ -635,6 +676,38 @@ class UsersTable:
except Exception:
return None
def update_user_scim_by_id(
self,
id: str,
provider: str,
external_id: str,
db: Optional[Session] = None,
) -> Optional[UserModel]:
"""
Update or insert a SCIM provider/external_id pair into the user's scim JSON field.
Example resulting structure:
{
"microsoft": { "external_id": "abc" },
"okta": { "external_id": "def" }
}
"""
try:
with get_db_context(db) as db:
user = db.query(User).filter_by(id=id).first()
if not user:
return None
scim = user.scim or {}
scim[provider] = {"external_id": external_id}
db.query(User).filter_by(id=id).update({"scim": scim})
db.commit()
return UserModel.model_validate(user)
except Exception:
return None
def update_user_by_id(
self, id: str, updated: dict, db: Optional[Session] = None
) -> Optional[UserModel]:
@@ -762,6 +835,14 @@ class UsersTable:
)
return count
@staticmethod
def is_active(user: UserModel) -> bool:
"""Compute active status from an already-loaded UserModel (no DB hit)."""
if user.last_active_at:
three_minutes_ago = int(time.time()) - 180
return user.last_active_at >= three_minutes_ago
return False
def is_user_active(self, user_id: str, db: Optional[Session] = None) -> bool:
with get_db_context(db) as db:
user = db.query(User).filter_by(id=user_id).first()
+10 -10
View File
@@ -4,6 +4,7 @@ KEYS_TO_EXCLUDE = ["content", "pages", "tables", "paragraphs", "sections", "figu
def filter_metadata(metadata: dict[str, any]) -> dict[str, any]:
# Removes large/redundant fields from metadata dict.
metadata = {
key: value for key, value in metadata.items() if key not in KEYS_TO_EXCLUDE
}
@@ -13,16 +14,15 @@ def filter_metadata(metadata: dict[str, any]) -> dict[str, any]:
def process_metadata(
metadata: dict[str, any],
) -> dict[str, any]:
# Removes large fields and converts non-serializable types (datetime, list, dict) to strings.
result = {}
for key, value in metadata.items():
# Remove large fields
# Skip large fields
if key in KEYS_TO_EXCLUDE:
del metadata[key]
continue
# Convert non-serializable fields to strings
if (
isinstance(value, datetime)
or isinstance(value, list)
or isinstance(value, dict)
):
metadata[key] = str(value)
return metadata
if isinstance(value, (datetime, list, dict)):
result[key] = str(value)
else:
result[key] = value
return result
+8 -7
View File
@@ -639,13 +639,14 @@ def transcription_handler(request, file_path, metadata, user=None):
if user and ENABLE_FORWARD_USER_INFO_HEADERS:
headers = include_user_info_headers(headers, user)
r = requests.post(
url=f"{request.app.state.config.STT_OPENAI_API_BASE_URL}/audio/transcriptions",
headers=headers,
files={"file": (filename, open(file_path, "rb"))},
data=payload,
timeout=AIOHTTP_CLIENT_TIMEOUT,
)
with open(file_path, "rb") as audio_file:
r = requests.post(
url=f"{request.app.state.config.STT_OPENAI_API_BASE_URL}/audio/transcriptions",
headers=headers,
files={"file": (filename, audio_file)},
data=payload,
timeout=AIOHTTP_CLIENT_TIMEOUT,
)
if r.status_code == 200:
# Successful transcription
+2 -2
View File
@@ -145,7 +145,7 @@ def create_session_response(
"email": user.email,
"name": user.name,
"role": user.role,
"profile_image_url": user.profile_image_url,
"profile_image_url": f"/api/v1/users/{user.id}/profile/image",
"permissions": user_permissions,
}
@@ -926,7 +926,7 @@ async def add_user(
"email": user.email,
"name": user.name,
"role": user.role,
"profile_image_url": user.profile_image_url,
"profile_image_url": f"/api/v1/users/{user.id}/profile/image",
}
else:
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
+4 -8
View File
@@ -204,7 +204,7 @@ async def get_channels(
UserIdNameStatusResponse(
**{
**user.model_dump(),
"is_active": Users.is_user_active(user.id, db=db),
"is_active": Users.is_active(user),
}
)
for user in Users.get_users_by_user_ids(user_ids, db=db)
@@ -424,7 +424,7 @@ async def get_channel_by_id(
UserIdNameStatusResponse(
**{
**user.model_dump(),
"is_active": Users.is_user_active(user.id, db=db),
"is_active": Users.is_active(user),
}
)
for user in Users.get_users_by_user_ids(user_ids, db=db)
@@ -540,9 +540,7 @@ async def get_channel_members_by_id(
return {
"users": [
UserModelResponse(
**user.model_dump(), is_active=Users.is_user_active(user.id, db=db)
)
UserModelResponse(**user.model_dump(), is_active=Users.is_active(user))
for user in users
],
"total": total,
@@ -575,9 +573,7 @@ async def get_channel_members_by_id(
return {
"users": [
UserModelResponse(
**user.model_dump(), is_active=Users.is_user_active(user.id, db=db)
)
UserModelResponse(**user.model_dump(), is_active=Users.is_active(user))
for user in users
],
"total": total,
+13 -23
View File
@@ -1131,9 +1131,9 @@ async def delete_chat_by_id(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id, db=db) == 1:
Tags.delete_tag_by_name_and_user_id(tag, user.id, db=db)
Chats.delete_orphan_tags_for_user(
chat.meta.get("tags", []), user.id, threshold=1, db=db
)
result = Chats.delete_chat_by_id(id, db=db)
@@ -1153,9 +1153,9 @@ async def delete_chat_by_id(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
)
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id, db=db) == 1:
Tags.delete_tag_by_name_and_user_id(tag, user.id, db=db)
Chats.delete_orphan_tags_for_user(
chat.meta.get("tags", []), user.id, threshold=1, db=db
)
result = Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
return result
@@ -1317,21 +1317,13 @@ async def archive_chat_by_id(
if chat:
chat = Chats.toggle_chat_archive_by_id(id, db=db)
# Delete tags if chat is archived
tag_ids = chat.meta.get("tags", [])
if chat.archived:
for tag_id in chat.meta.get("tags", []):
if (
Chats.count_chats_by_tag_name_and_user_id(tag_id, user.id, db=db)
== 0
):
log.debug(f"deleting tag: {tag_id}")
Tags.delete_tag_by_name_and_user_id(tag_id, user.id, db=db)
# Archived chats are excluded from count — clean up orphans
Chats.delete_orphan_tags_for_user(tag_ids, user.id, db=db)
else:
for tag_id in chat.meta.get("tags", []):
tag = Tags.get_tag_by_name_and_user_id(tag_id, user.id, db=db)
if tag is None:
log.debug(f"inserting tag: {tag_id}")
tag = Tags.insert_new_tag(tag_id, user.id, db=db)
# Unarchived — ensure tag rows exist
Tags.ensure_tags_exist(tag_ids, user.id, db=db)
return ChatResponse(**chat.model_dump())
else:
@@ -1537,11 +1529,9 @@ async def delete_all_tags_by_id(
):
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
if chat:
old_tags = chat.meta.get("tags", [])
Chats.delete_all_tags_by_id_and_user_id(id, user.id, db=db)
for tag in chat.meta.get("tags", []):
if Chats.count_chats_by_tag_name_and_user_id(tag, user.id, db=db) == 0:
Tags.delete_tag_by_name_and_user_id(tag, user.id, db=db)
Chats.delete_orphan_tags_for_user(old_tags, user.id, db=db)
return True
else:
+4
View File
@@ -849,6 +849,7 @@ class EditImageForm(BaseModel):
size: Optional[str] = None
n: Optional[int] = None
negative_prompt: Optional[str] = None
background: Optional[str] = None
@router.post("/edit")
@@ -953,6 +954,9 @@ async def image_edits(
"prompt": form_data.prompt,
**({"n": form_data.n} if form_data.n else {}),
**({"size": size} if size else {}),
**(
{"background": form_data.background} if form_data.background else {}
),
**(
{}
if re.match(
+50 -16
View File
@@ -115,8 +115,10 @@ async def get_knowledge_bases(
skip = (page - 1) * limit
filter = {}
groups = Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == "admin" or not BYPASS_ADMIN_ACCESS_CONTROL:
groups = Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter["group_ids"] = [group.id for group in groups]
@@ -126,6 +128,17 @@ async def get_knowledge_bases(
user.id, filter=filter, skip=skip, limit=limit, db=db
)
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="knowledge",
resource_ids=knowledge_base_ids,
permission="write",
user_group_ids=user_group_ids,
db=db,
)
return KnowledgeAccessListResponse(
items=[
KnowledgeAccessResponse(
@@ -133,13 +146,7 @@ async def get_knowledge_bases(
write_access=(
user.id == knowledge_base.user_id
or (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or AccessGrants.has_access(
user_id=user.id,
resource_type="knowledge",
resource_id=knowledge_base.id,
permission="write",
db=db,
)
or knowledge_base.id in writable_knowledge_base_ids
),
)
for knowledge_base in result.items
@@ -166,8 +173,10 @@ async def search_knowledge_bases(
if view_option:
filter["view_option"] = view_option
groups = Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == "admin" or not BYPASS_ADMIN_ACCESS_CONTROL:
groups = Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter["group_ids"] = [group.id for group in groups]
@@ -177,6 +186,17 @@ async def search_knowledge_bases(
user.id, filter=filter, skip=skip, limit=limit, db=db
)
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="knowledge",
resource_ids=knowledge_base_ids,
permission="write",
user_group_ids=user_group_ids,
db=db,
)
return KnowledgeAccessListResponse(
items=[
KnowledgeAccessResponse(
@@ -184,13 +204,7 @@ async def search_knowledge_bases(
write_access=(
user.id == knowledge_base.user_id
or (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or AccessGrants.has_access(
user_id=user.id,
resource_type="knowledge",
resource_id=knowledge_base.id,
permission="write",
db=db,
)
or knowledge_base.id in writable_knowledge_base_ids
),
)
for knowledge_base in result.items
@@ -511,6 +525,7 @@ class KnowledgeAccessGrantsForm(BaseModel):
@router.post("/{id}/access/update", response_model=Optional[KnowledgeFilesResponse])
async def update_knowledge_access_by_id(
request: Request,
id: str,
form_data: KnowledgeAccessGrantsForm,
user=Depends(get_verified_user),
@@ -539,6 +554,25 @@ async def update_knowledge_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_knowledge",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants("knowledge", id, form_data.access_grants, db=db)
return KnowledgeFilesResponse(
+62 -12
View File
@@ -8,14 +8,16 @@ import logging
from open_webui.models.groups import Groups
from open_webui.models.models import (
ModelForm,
ModelMeta,
ModelModel,
ModelParams,
ModelResponse,
ModelListResponse,
ModelAccessListResponse,
ModelAccessResponse,
Models,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant
from pydantic import BaseModel
from open_webui.constants import ERROR_MESSAGES
@@ -84,14 +86,29 @@ async def get_models(
if direction:
filter["direction"] = direction
# Pre-fetch user group IDs once - used for both filter and write_access check
groups = Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not user.role == "admin" or not BYPASS_ADMIN_ACCESS_CONTROL:
groups = Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter["group_ids"] = [group.id for group in groups]
filter["user_id"] = user.id
result = Models.search_models(user.id, filter=filter, skip=skip, limit=limit, db=db)
# Batch-fetch writable model IDs in a single query instead of N has_access calls
model_ids = [model.id for model in result.items]
writable_model_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="model",
resource_ids=model_ids,
permission="write",
user_group_ids=user_group_ids,
db=db,
)
return ModelAccessListResponse(
items=[
ModelAccessResponse(
@@ -99,13 +116,7 @@ async def get_models(
write_access=(
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == model.user_id
or AccessGrants.has_access(
user_id=user.id,
resource_type="model",
resource_id=model.id,
permission="write",
db=db,
)
or model.id in writable_model_ids
),
)
for model in result.items
@@ -506,16 +517,36 @@ class ModelAccessGrantsForm(BaseModel):
@router.post("/model/access/update", response_model=Optional[ModelModel])
async def update_model_access_by_id(
request: Request,
form_data: ModelAccessGrantsForm,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
model = Models.get_model_by_id(form_data.id, db=db)
# Non-preset models (e.g. direct Ollama/OpenAI models) may not have a DB
# entry yet. Create a minimal one so access grants can be stored.
if not model:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.NOT_FOUND,
if user.role != "admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
model = Models.insert_new_model(
ModelForm(
id=form_data.id,
name=form_data.id,
meta=ModelMeta(),
params=ModelParams(),
),
user.id,
db=db,
)
if not model:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=ERROR_MESSAGES.DEFAULT("Error creating model entry"),
)
if (
model.user_id != user.id
@@ -533,6 +564,25 @@ async def update_model_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_models",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants(
"model", form_data.id, form_data.access_grants, db=db
)
+19
View File
@@ -345,6 +345,25 @@ async def update_note_access_by_id(
status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_notes",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants("note", id, form_data.access_grants, db=db)
return Notes.get_note_by_id(id, db=db)
+50 -38
View File
@@ -418,21 +418,29 @@ async def get_all_models(request: Request, user: UserModel = None):
async def get_filtered_models(models, user, db=None):
# Filter models based on user access control
model_ids = [model["model"] for model in models.get("models", [])]
model_infos = {m.id: m for m in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {g.id for g in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {
model_info.id: model_info
for model_info in Models.get_models_by_ids(model_ids, db=db)
}
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="model",
resource_ids=list(model_infos.keys()),
permission="read",
user_group_ids=user_group_ids,
db=db,
)
filtered_models = []
for model in models.get("models", []):
model_info = model_infos.get(model["model"])
if model_info:
if user.id == model_info.user_id or AccessGrants.has_access(
user_id=user.id,
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
db=db,
):
if user.id == model_info.user_id or model_info.id in accessible_model_ids:
filtered_models.append(model)
return filtered_models
@@ -653,10 +661,6 @@ async def unload_model(
await get_all_models(request, user=user)
models = request.app.state.OLLAMA_MODELS
# Canonicalize model name (if not supplied with version)
if ":" not in model_name:
model_name = f"{model_name}:latest"
if model_name not in models:
raise HTTPException(
status_code=400, detail=ERROR_MESSAGES.MODEL_NOT_FOUND(model_name)
@@ -1333,6 +1337,9 @@ async def generate_chat_completion(
# Check if user has access to the model
if not bypass_filter and user.role == "user":
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id)
}
if not (
user.id == model_info.user_id
or AccessGrants.has_access(
@@ -1340,6 +1347,7 @@ async def generate_chat_completion(
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
)
):
raise HTTPException(
@@ -1353,9 +1361,6 @@ async def generate_chat_completion(
detail="Model not found",
)
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
@@ -1432,9 +1437,6 @@ async def generate_openai_completion(
del payload["metadata"]
model_id = form_data.model
if ":" not in model_id:
model_id = f"{model_id}:latest"
model_info = Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
@@ -1446,6 +1448,9 @@ async def generate_openai_completion(
# Check if user has access to the model
if user.role == "user":
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id)
}
if not (
user.id == model_info.user_id
or AccessGrants.has_access(
@@ -1453,6 +1458,7 @@ async def generate_openai_completion(
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
)
):
raise HTTPException(
@@ -1466,9 +1472,6 @@ async def generate_openai_completion(
detail="Model not found",
)
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
@@ -1518,9 +1521,6 @@ async def generate_openai_chat_completion(
del payload["metadata"]
model_id = completion_form.model
if ":" not in model_id:
model_id = f"{model_id}:latest"
model_info = Models.get_model_by_id(model_id)
if model_info:
if model_info.base_model_id:
@@ -1536,6 +1536,9 @@ async def generate_openai_chat_completion(
# Check if user has access to the model
if user.role == "user":
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id)
}
if not (
user.id == model_info.user_id
or AccessGrants.has_access(
@@ -1543,6 +1546,7 @@ async def generate_openai_chat_completion(
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
)
):
raise HTTPException(
@@ -1556,9 +1560,6 @@ async def generate_openai_chat_completion(
detail="Model not found",
)
if ":" not in payload["model"]:
payload["model"] = f"{payload['model']}:latest"
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
str(url_idx),
@@ -1637,20 +1638,31 @@ async def get_openai_models(
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
# Filter models based on user access control
model_ids = [model["id"] for model in models]
model_infos = {m.id: m for m in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {g.id for g in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {
model_info.id: model_info
for model_info in Models.get_models_by_ids(model_ids, db=db)
}
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="model",
resource_ids=list(model_infos.keys()),
permission="read",
user_group_ids=user_group_ids,
db=db,
)
filtered_models = []
for model in models:
model_info = model_infos.get(model["id"])
if model_info:
if user.id == model_info.user_id or AccessGrants.has_access(
user_id=user.id,
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
db=db,
if (
user.id == model_info.user_id
or model_info.id in accessible_model_ids
):
filtered_models.append(model)
models = filtered_models
+169 -14
View File
@@ -18,7 +18,7 @@ from fastapi.responses import (
JSONResponse,
PlainTextResponse,
)
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict
from sqlalchemy.orm import Session
@@ -455,21 +455,29 @@ async def get_all_models_responses(request: Request, user: UserModel) -> list:
async def get_filtered_models(models, user, db=None):
# Filter models based on user access control
model_ids = [model["id"] for model in models.get("data", [])]
model_infos = {m.id: m for m in Models.get_models_by_ids(model_ids, db=db)}
user_group_ids = {g.id for g in Groups.get_groups_by_member_id(user.id, db=db)}
model_infos = {
model_info.id: model_info
for model_info in Models.get_models_by_ids(model_ids, db=db)
}
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="model",
resource_ids=list(model_infos.keys()),
permission="read",
user_group_ids=user_group_ids,
db=db,
)
filtered_models = []
for model in models.get("data", []):
model_info = model_infos.get(model["id"])
if model_info:
if user.id == model_info.user_id or AccessGrants.has_access(
user_id=user.id,
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
db=db,
):
if user.id == model_info.user_id or model_info.id in accessible_model_ids:
filtered_models.append(model)
return filtered_models
@@ -596,9 +604,12 @@ async def get_models(
if r.status != 200:
# Extract response error details if available
error_detail = f"HTTP Error: {r.status}"
res = await r.json()
if "error" in res:
error_detail = f"External Error: {res['error']}"
try:
res = await r.json()
if "error" in res:
error_detail = f"External Error: {res['error']}"
except Exception:
pass
raise Exception(error_detail)
response_data = await r.json()
@@ -960,6 +971,9 @@ async def generate_chat_completion(
# Check if user has access to the model
if not bypass_filter and user.role == "user":
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id)
}
if not (
user.id == model_info.user_id
or AccessGrants.has_access(
@@ -967,6 +981,7 @@ async def generate_chat_completion(
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
)
):
raise HTTPException(
@@ -1208,6 +1223,129 @@ async def embeddings(request: Request, form_data: dict, user):
await cleanup_response(r, session)
class ResponsesForm(BaseModel):
model_config = ConfigDict(extra="allow")
model: str
input: Optional[list | str] = None
instructions: Optional[str] = None
stream: Optional[bool] = None
temperature: Optional[float] = None
max_output_tokens: Optional[int] = None
top_p: Optional[float] = None
tools: Optional[list] = None
tool_choice: Optional[str | dict] = None
text: Optional[dict] = None
truncation: Optional[str] = None
metadata: Optional[dict] = None
store: Optional[bool] = None
reasoning: Optional[dict] = None
previous_response_id: Optional[str] = None
@router.post("/responses")
async def responses(
request: Request,
form_data: ResponsesForm,
user=Depends(get_verified_user),
):
"""
Forward requests to the OpenAI Responses API endpoint.
Routes to the correct upstream backend based on the model field.
"""
payload = form_data.model_dump(exclude_none=True)
body = json.dumps(payload)
idx = 0
model_id = form_data.model
if model_id:
models = request.app.state.OPENAI_MODELS
if not models or model_id not in models:
await get_all_models(request, user=user)
models = request.app.state.OPENAI_MODELS
if model_id in models:
idx = models[model_id]["urlIdx"]
url = request.app.state.config.OPENAI_API_BASE_URLS[idx]
key = request.app.state.config.OPENAI_API_KEYS[idx]
api_config = request.app.state.config.OPENAI_API_CONFIGS.get(
str(idx),
request.app.state.config.OPENAI_API_CONFIGS.get(url, {}), # Legacy support
)
r = None
session = None
streaming = False
try:
headers, cookies = await get_headers_and_cookies(
request, url, key, api_config, user=user
)
if api_config.get("azure", False):
api_version = api_config.get("api_version", "2023-03-15-preview")
auth_type = api_config.get("auth_type", "bearer")
if auth_type not in ("azure_ad", "microsoft_entra_id"):
headers["api-key"] = key
headers["api-version"] = api_version
model = payload.get("model", "")
request_url = (
f"{url}/openai/deployments/{model}/responses?api-version={api_version}"
)
else:
request_url = f"{url}/responses"
session = aiohttp.ClientSession(
trust_env=True,
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
)
r = await session.request(
method="POST",
url=request_url,
data=body,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
)
# Check if response is SSE
if "text/event-stream" in r.headers.get("Content-Type", ""):
streaming = True
return StreamingResponse(
stream_wrapper(r, session),
status_code=r.status,
headers=dict(r.headers),
)
else:
try:
response_data = await r.json()
except Exception:
response_data = await r.text()
if r.status >= 400:
if isinstance(response_data, (dict, list)):
return JSONResponse(status_code=r.status, content=response_data)
else:
return PlainTextResponse(
status_code=r.status, content=response_data
)
return response_data
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=r.status if r else 500,
detail="Open WebUI: Server Connection Error",
)
finally:
if not streaming:
await cleanup_response(r, session)
@router.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
"""
@@ -1216,7 +1354,24 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
body = await request.body()
# Parse JSON body to resolve model-based routing
payload = None
if body:
try:
payload = json.loads(body)
except (json.JSONDecodeError, ValueError):
payload = None
idx = 0
model_id = payload.get("model") if isinstance(payload, dict) else None
if model_id:
models = request.app.state.OPENAI_MODELS
if not models or model_id not in models:
await get_all_models(request, user=user)
models = request.app.state.OPENAI_MODELS
if model_id in models:
idx = models[model_id]["urlIdx"]
url = request.app.state.config.OPENAI_API_BASE_URLS[idx]
key = request.app.state.config.OPENAI_API_KEYS[idx]
api_config = request.app.state.config.OPENAI_API_CONFIGS.get(
+16 -15
View File
@@ -228,22 +228,23 @@ async def upload_pipeline(
headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession(trust_env=True) as session:
form_data = aiohttp.FormData()
form_data.add_field(
"file",
open(file_path, "rb"),
filename=filename,
content_type="application/octet-stream",
)
with open(file_path, "rb") as f:
form_data = aiohttp.FormData()
form_data.add_field(
"file",
f,
filename=filename,
content_type="application/octet-stream",
)
async with session.post(
f"{url}/pipelines/upload",
headers=headers,
data=form_data,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as response:
response.raise_for_status()
data = await response.json()
async with session.post(
f"{url}/pipelines/upload",
headers=headers,
data=form_data,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as response:
response.raise_for_status()
data = await response.json()
return {**data}
except Exception as e:
+37 -9
View File
@@ -9,7 +9,7 @@ from open_webui.models.prompts import (
PromptModel,
Prompts,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant
from open_webui.models.groups import Groups
from open_webui.models.prompt_history import (
PromptHistories,
@@ -100,8 +100,11 @@ async def get_prompt_list(
if direction:
filter["direction"] = direction
# Pre-fetch user group IDs once - used for both filter and write_access check
groups = Groups.get_groups_by_member_id(user.id, db=db)
user_group_ids = {group.id for group in groups}
if not (user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL):
groups = Groups.get_groups_by_member_id(user.id, db=db)
if groups:
filter["group_ids"] = [group.id for group in groups]
@@ -111,6 +114,17 @@ async def get_prompt_list(
user.id, filter=filter, skip=skip, limit=limit, db=db
)
# Batch-fetch writable prompt IDs in a single query instead of N has_access calls
prompt_ids = [prompt.id for prompt in result.items]
writable_prompt_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="prompt",
resource_ids=prompt_ids,
permission="write",
user_group_ids=user_group_ids,
db=db,
)
return PromptAccessListResponse(
items=[
PromptAccessResponse(
@@ -118,13 +132,7 @@ async def get_prompt_list(
write_access=(
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == prompt.user_id
or AccessGrants.has_access(
user_id=user.id,
resource_type="prompt",
resource_id=prompt.id,
permission="write",
db=db,
)
or prompt.id in writable_prompt_ids
),
)
for prompt in result.items
@@ -436,6 +444,7 @@ class PromptAccessGrantsForm(BaseModel):
@router.post("/id/{prompt_id}/access/update", response_model=Optional[PromptModel])
async def update_prompt_access_by_id(
request: Request,
prompt_id: str,
form_data: PromptAccessGrantsForm,
user=Depends(get_verified_user),
@@ -464,6 +473,25 @@ async def update_prompt_access_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_prompts",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants("prompt", prompt_id, form_data.access_grants, db=db)
return Prompts.get_prompt_by_id(prompt_id, db=db)
+82 -8
View File
@@ -25,6 +25,9 @@ from open_webui.utils.auth import (
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.config import OAUTH_PROVIDERS
from open_webui.env import SCIM_AUTH_PROVIDER
from sqlalchemy.orm import Session
from open_webui.internal.db import get_session
@@ -300,6 +303,43 @@ def get_scim_auth(
)
def get_external_id(user: UserModel) -> Optional[str]:
"""Extract externalId from a user's scim data.
Checks all stored provider entries and returns the first external_id found.
"""
if not user.scim:
return None
for provider_data in user.scim.values():
if isinstance(provider_data, dict) and "external_id" in provider_data:
return provider_data["external_id"]
return None
def get_scim_provider() -> str:
"""Return the configured SCIM auth provider.
Requires SCIM_AUTH_PROVIDER env var to be set (e.g. 'microsoft', 'oidc').
"""
if not SCIM_AUTH_PROVIDER:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="SCIM_AUTH_PROVIDER environment variable is required when SCIM is enabled",
)
return SCIM_AUTH_PROVIDER
def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
"""Find a user by SCIM externalId, falling back to OAuth sub match."""
provider = get_scim_provider()
user = Users.get_user_by_scim_external_id(provider, external_id, db=db)
if user:
return user
# Fallback: check if externalId matches an existing OAuth sub (account linking)
return Users.get_user_by_oauth_sub(provider, external_id, db=db)
def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
"""Convert internal User model to SCIM User"""
# Parse display name into name components
@@ -321,6 +361,7 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
return SCIMUser(
id=user.id,
externalId=get_external_id(user),
userName=user.email,
name=SCIMName(
formatted=user.name,
@@ -494,13 +535,17 @@ async def get_users(
# Get users from database
if filter:
# Simple filter parsing - supports userName eq "email"
# In production, you'd want a more robust filter parser
# Simple filter parsing - supports userName eq, externalId eq
if "userName eq" in filter:
email = filter.split('"')[1]
user = Users.get_user_by_email(email, db=db)
users_list = [user] if user else []
total = 1 if user else 0
elif "externalId eq" in filter:
external_id = filter.split('"')[1]
user = find_user_by_external_id(external_id, db=db)
users_list = [user] if user else []
total = 1 if user else 0
else:
response = Users.get_users(skip=skip, limit=limit, db=db)
users_list = response["users"]
@@ -546,17 +591,33 @@ async def create_user(
db: Session = Depends(get_session),
):
"""Create SCIM User"""
# Check if user already exists
existing_user = Users.get_user_by_email(user_data.userName, db=db)
# Check for duplicate by externalId
if user_data.externalId:
existing_user = find_user_by_external_id(user_data.externalId, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"User with externalId {user_data.externalId} already exists",
)
# Determine primary email (lowercased per RFC 5321)
email = user_data.userName
for entry in user_data.emails:
if entry.primary:
email = entry.value
break
email = email.lower()
# Check for duplicate by email
existing_user = Users.get_user_by_email(email, db=db)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"User with email {user_data.userName} already exists",
detail=f"User with email {email} already exists",
)
# Create user
user_id = str(uuid.uuid4())
email = user_data.emails[0].value if user_data.emails else user_data.userName
# Parse name if provided
name = user_data.displayName
@@ -571,7 +632,6 @@ async def create_user(
if user_data.photos and len(user_data.photos) > 0:
profile_image = user_data.photos[0].value
# Create user
new_user = Users.insert_new_user(
id=user_id,
name=name,
@@ -587,6 +647,12 @@ async def create_user(
detail="Failed to create user",
)
# Store externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
new_user = Users.get_user_by_id(user_id, db=db)
return user_to_scim(new_user, request, db=db)
@@ -631,7 +697,6 @@ async def update_user(
if user_data.photos and len(user_data.photos) > 0:
update_data["profile_image_url"] = user_data.photos[0].value
# Update user
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
if not updated_user:
raise HTTPException(
@@ -639,6 +704,12 @@ async def update_user(
detail="Failed to update user",
)
# Update externalId in the scim field
if user_data.externalId:
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
updated_user = Users.get_user_by_id(user_id, db=db)
return user_to_scim(updated_user, request, db=db)
@@ -676,6 +747,9 @@ async def patch_user(
update_data["email"] = value
elif path == "name.formatted":
update_data["name"] = value
elif path == "externalId":
provider = get_scim_provider()
Users.update_user_scim_by_id(user_id, provider, value, db=db)
# Update user
if update_data:
+21 -1
View File
@@ -17,7 +17,7 @@ from open_webui.models.skills import (
SkillAccessListResponse,
Skills,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_access, has_permission
@@ -312,6 +312,7 @@ class SkillAccessGrantsForm(BaseModel):
@router.post("/id/{id}/access/update", response_model=Optional[SkillModel])
async def update_skill_access_by_id(
request: Request,
id: str,
form_data: SkillAccessGrantsForm,
user=Depends(get_verified_user),
@@ -340,6 +341,25 @@ async def update_skill_access_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_skills",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants("skill", id, form_data.access_grants, db=db)
return Skills.get_skill_by_id(id, db=db)
+24 -2
View File
@@ -21,7 +21,7 @@ from open_webui.models.tools import (
ToolAccessResponse,
Tools,
)
from open_webui.models.access_grants import AccessGrants
from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant
from open_webui.utils.plugin import (
load_tool_module_by_id,
replace_imports,
@@ -107,7 +107,9 @@ async def get_tools(
# MCP Tool Servers
for server in request.app.state.config.TOOL_SERVER_CONNECTIONS:
if server.get("type", "openapi") == "mcp":
if server.get("type", "openapi") == "mcp" and server.get("config", {}).get(
"enable"
):
server_id = server.get("info", {}).get("id")
auth_type = server.get("auth_type", "none")
@@ -526,6 +528,7 @@ class ToolAccessGrantsForm(BaseModel):
@router.post("/id/{id}/access/update", response_model=Optional[ToolModel])
async def update_tool_access_by_id(
request: Request,
id: str,
form_data: ToolAccessGrantsForm,
user=Depends(get_verified_user),
@@ -554,6 +557,25 @@ async def update_tool_access_by_id(
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
# Strip public sharing if user lacks permission
if (
user.role != "admin"
and has_public_read_access_grant(form_data.access_grants)
and not has_permission(
user.id,
"sharing.public_tools",
request.app.state.config.USER_PERMISSIONS,
)
):
form_data.access_grants = [
grant
for grant in form_data.access_grants
if not (
grant.get("principal_type") == "user"
and grant.get("principal_id") == "*"
)
]
AccessGrants.set_access_grants("tool", id, form_data.access_grants, db=db)
return Tools.get_tool_by_id(id, db=db)
+2
View File
@@ -190,6 +190,8 @@ class SharingPermissions(BaseModel):
public_prompts: bool = False
tools: bool = False
public_tools: bool = True
skills: bool = False
public_skills: bool = False
notes: bool = False
public_notes: bool = True
+74 -12
View File
@@ -99,6 +99,7 @@ else:
# Timeout duration in seconds
TIMEOUT_DURATION = 3
SESSION_POOL_TIMEOUT = 120 # seconds without heartbeat before session is reaped
# Dictionary to maintain the user pool
@@ -147,6 +148,17 @@ if WEBSOCKET_MANAGER == "redis":
aquire_func = clean_up_lock.aquire_lock
renew_func = clean_up_lock.renew_lock
release_func = clean_up_lock.release_lock
session_cleanup_lock = RedisLock(
redis_url=WEBSOCKET_REDIS_URL,
lock_name=f"{REDIS_KEY_PREFIX}:session_cleanup_lock",
timeout_secs=WEBSOCKET_REDIS_LOCK_TIMEOUT,
redis_sentinels=redis_sentinels,
redis_cluster=WEBSOCKET_REDIS_CLUSTER,
)
session_aquire_func = session_cleanup_lock.aquire_lock
session_renew_func = session_cleanup_lock.renew_lock
session_release_func = session_cleanup_lock.release_lock
else:
MODELS = {}
@@ -154,6 +166,7 @@ else:
USAGE_POOL = {}
aquire_func = release_func = renew_func = lambda: True
session_aquire_func = session_release_func = session_renew_func = lambda: True
YDOC_MANAGER = YdocManager(
@@ -162,6 +175,31 @@ YDOC_MANAGER = YdocManager(
)
async def periodic_session_pool_cleanup():
"""Reap orphaned SESSION_POOL entries that missed heartbeats (e.g. crashed instance)."""
if not session_aquire_func():
log.debug("Session cleanup lock held by another node. Skipping.")
return
try:
while True:
if not session_renew_func():
log.error("Unable to renew session cleanup lock. Exiting.")
return
now = int(time.time())
for sid in list(SESSION_POOL.keys()):
entry = SESSION_POOL.get(sid)
if entry and now - entry.get("last_seen_at", 0) > SESSION_POOL_TIMEOUT:
log.warning(
f"Reaping orphaned session {sid} (user {entry.get('id')})"
)
del SESSION_POOL[sid]
await asyncio.sleep(SESSION_POOL_TIMEOUT)
finally:
session_release_func()
async def periodic_usage_pool_cleanup():
max_retries = 2
retry_delay = random.uniform(
@@ -313,9 +351,18 @@ async def connect(sid, environ, auth):
user = Users.get_user_by_id(data["id"])
if user:
SESSION_POOL[sid] = user.model_dump(
exclude=["date_of_birth", "bio", "gender"]
)
SESSION_POOL[sid] = {
**user.model_dump(
exclude=[
"profile_image_url",
"profile_banner_image_url",
"date_of_birth",
"bio",
"gender",
]
),
"last_seen_at": int(time.time()),
}
await sio.enter_room(sid, f"user:{user.id}")
@@ -334,15 +381,18 @@ async def user_join(sid, data):
if not user:
return
SESSION_POOL[sid] = user.model_dump(
exclude=[
"profile_image_url",
"profile_banner_image_url",
"date_of_birth",
"bio",
"gender",
]
)
SESSION_POOL[sid] = {
**user.model_dump(
exclude=[
"profile_image_url",
"profile_banner_image_url",
"date_of_birth",
"bio",
"gender",
]
),
"last_seen_at": int(time.time()),
}
await sio.enter_room(sid, f"user:{user.id}")
@@ -360,6 +410,7 @@ async def user_join(sid, data):
async def heartbeat(sid, data):
user = SESSION_POOL.get(sid)
if user:
SESSION_POOL[sid] = {**user, "last_seen_at": int(time.time())}
Users.update_last_active_by_id(user["id"])
@@ -703,6 +754,17 @@ async def disconnect(sid):
if sid in SESSION_POOL:
user = SESSION_POOL[sid]
del SESSION_POOL[sid]
# Clean up USAGE_POOL entries for this session
for model_id in list(USAGE_POOL.keys()):
connections = USAGE_POOL.get(model_id)
if connections and sid in connections:
del connections[sid]
if not connections:
del USAGE_POOL[model_id]
else:
USAGE_POOL[model_id] = connections
await YDOC_MANAGER.remove_user_from_all_documents(sid)
else:
pass
+34
View File
@@ -118,6 +118,8 @@ class RedisDict:
class YdocManager:
COMPACTION_THRESHOLD = 500
def __init__(
self,
redis=None,
@@ -133,10 +135,42 @@ class YdocManager:
if self._redis:
redis_key = f"{self._redis_key_prefix}:{document_id}:updates"
await self._redis.rpush(redis_key, json.dumps(list(update)))
list_len = await self._redis.llen(redis_key)
if list_len >= self.COMPACTION_THRESHOLD:
await self._compact_updates_redis(document_id)
else:
if document_id not in self._updates:
self._updates[document_id] = []
self._updates[document_id].append(update)
if len(self._updates[document_id]) >= self.COMPACTION_THRESHOLD:
self._compact_updates_memory(document_id)
async def _compact_updates_redis(self, document_id: str):
"""Rolling compaction: squash oldest half into one snapshot."""
redis_key = f"{self._redis_key_prefix}:{document_id}:updates"
all_updates = await self._redis.lrange(redis_key, 0, -1)
if len(all_updates) <= 1:
return
mid = len(all_updates) // 2
ydoc = Y.Doc()
for raw in all_updates[:mid]:
ydoc.apply_update(bytes(json.loads(raw)))
snapshot = json.dumps(list(ydoc.get_update()))
pipe = self._redis.pipeline()
pipe.delete(redis_key)
pipe.rpush(redis_key, snapshot, *all_updates[mid:])
await pipe.execute()
def _compact_updates_memory(self, document_id: str):
"""Rolling compaction: squash oldest half into one snapshot."""
updates = self._updates.get(document_id, [])
if len(updates) <= 1:
return
mid = len(updates) // 2
ydoc = Y.Doc()
for update in updates[:mid]:
ydoc.apply_update(bytes(update))
self._updates[document_id] = [ydoc.get_update()] + updates[mid:]
async def get_updates(self, document_id: str) -> List[bytes]:
document_id = document_id.replace(":", "_")
@@ -12,7 +12,7 @@ def _assert_user(data, id, **kwargs):
comparison_data = {
"name": f"user {id}",
"email": f"user{id}@openwebui.com",
"profile_image_url": f"/user{id}.png",
"profile_image_url": f"/api/v1/users/{id}/profile/image",
"role": "user",
**kwargs,
}
@@ -150,7 +150,7 @@ class TestUsers(AbstractPostgresTest):
role="admin",
name="user 2 updated",
email="user2-updated@openwebui.com",
profile_image_url="/user2-updated.png",
profile_image_url=f"/api/v1/users/2/profile/image",
)
# Delete user by id
+66
View File
@@ -167,6 +167,9 @@ async def search_web(
engine = __request__.app.state.config.WEB_SEARCH_ENGINE
user = UserModel(**__user__) if __user__ else None
# Use admin-configured result count if configured, falling back to model-provided count of provided, else default to 5
count = __request__.app.state.config.WEB_SEARCH_RESULT_COUNT or count
results = await asyncio.to_thread(_search_web, __request__, engine, query, user)
# Limit results
@@ -1529,6 +1532,69 @@ async def search_knowledge_files(
return json.dumps({"error": str(e)})
async def view_file(
file_id: str,
__request__: Request = None,
__user__: dict = None,
__model_knowledge__: Optional[list[dict]] = None,
) -> str:
"""
Get the full content of a file by its ID.
:param file_id: The ID of the file to retrieve
:return: JSON with the file's id, filename, and full text content
"""
if __request__ is None:
return json.dumps({"error": "Request context not available"})
if not __user__:
return json.dumps({"error": "User context not available"})
try:
from open_webui.models.files import Files
from open_webui.routers.files import has_access_to_file
user_id = __user__.get("id")
user_role = __user__.get("role", "user")
file = Files.get_file_by_id(file_id)
if not file:
return json.dumps({"error": "File not found"})
if (
file.user_id != user_id
and user_role != "admin"
and not any(
item.get("type") == "file" and item.get("id") == file_id
for item in (__model_knowledge__ or [])
)
and not has_access_to_file(
file_id=file_id,
access_type="read",
user=UserModel(**__user__),
)
):
return json.dumps({"error": "File not found"})
content = ""
if file.data:
content = file.data.get("content", "")
return json.dumps(
{
"id": file.id,
"filename": file.filename,
"content": content,
"updated_at": file.updated_at,
"created_at": file.created_at,
},
ensure_ascii=False,
)
except Exception as e:
log.exception(f"view_file error: {e}")
return json.dumps({"error": str(e)})
async def view_knowledge_file(
file_id: str,
__request__: Request = None,
-5
View File
@@ -39,11 +39,6 @@ from open_webui.routers.pipelines import (
from open_webui.models.functions import Functions
from open_webui.models.models import Models
from open_webui.utils.plugin import (
load_function_module_by_id,
get_function_module_from_cache,
)
from open_webui.utils.models import get_all_models, check_model_access
from open_webui.utils.payload import convert_payload_openai_to_ollama
from open_webui.utils.response import (
+273 -126
View File
@@ -411,6 +411,36 @@ def serialize_output(output: list) -> str:
if content and not content.endswith("\n"):
content += "\n"
# Render the code_interpreter item as a <details> block
# so the frontend Collapsible renders "Analyzing..."/"Analyzed".
code = item.get("code", "").strip()
lang = item.get("lang", "python")
status = item.get("status", "in_progress")
duration = item.get("duration")
is_last_item = idx == len(output) - 1
# Build inner content: code block
display = ""
if code:
display = f"```{lang}\n{code}\n```"
# Build output attribute as HTML-escaped JSON for CodeBlock.svelte
ci_output = item.get("output")
output_attr = ""
if ci_output:
if isinstance(ci_output, dict):
output_json = json.dumps(ci_output, ensure_ascii=False)
else:
output_json = json.dumps(
{"result": str(ci_output)}, ensure_ascii=False
)
output_attr = f' output="{html.escape(output_json)}"'
if status == "completed" or duration is not None or not is_last_item:
content += f'<details type="code_interpreter" done="true" duration="{duration or 0}"{output_attr}>\n<summary>Analyzed</summary>\n{display}\n</details>\n'
else:
content += f'<details type="code_interpreter" done="false"{output_attr}>\n<summary>Analyzing…</summary>\n{display}\n</details>\n'
return content.strip()
@@ -1888,6 +1918,25 @@ async def convert_url_images_to_base64(form_data):
return form_data
def load_messages_from_db(chat_id: str, message_id: str) -> Optional[list[dict]]:
"""
Load the message chain from DB up to message_id,
keeping only LLM-relevant fields (role, content, output).
"""
messages_map = Chats.get_messages_map_by_chat_id(chat_id)
if not messages_map:
return None
db_messages = get_message_list(messages_map, message_id)
if not db_messages:
return None
return [
{k: v for k, v in msg.items() if k in ("role", "content", "output", "files")}
for msg in db_messages
]
def process_messages_with_output(messages: list[dict]) -> list[dict]:
"""
Process messages with OR-aligned output items for LLM consumption.
@@ -1920,6 +1969,44 @@ async def process_chat_payload(request, form_data, user, metadata, model):
form_data = apply_params_to_form_data(form_data, model)
log.debug(f"form_data: {form_data}")
# Load messages from DB when available — DB preserves structured 'output' items
# which the frontend strips, causing tool calls to be merged into content.
chat_id = metadata.get("chat_id")
parent_message_id = metadata.get("parent_message_id")
if chat_id and parent_message_id and not chat_id.startswith("local:"):
db_messages = load_messages_from_db(chat_id, parent_message_id)
if db_messages:
system_message = get_system_message(form_data.get("messages", []))
form_data["messages"] = (
[system_message, *db_messages] if system_message else db_messages
)
# Inject image files into content as image_url parts (mirrors frontend logic)
for message in form_data["messages"]:
image_files = [
f
for f in message.get("files", [])
if f.get("type") == "image"
or (f.get("content_type") or "").startswith("image/")
]
if message.get("role") == "user" and image_files:
text_content = message.get("content", "")
if isinstance(text_content, str):
message["content"] = [
{"type": "text", "text": text_content},
*[
{
"type": "image_url",
"image_url": {"url": f["url"]},
}
for f in image_files
if f.get("url")
],
]
# Strip files field — it's been incorporated into content
message.pop("files", None)
# Process messages with OR-aligned output items for clean LLM messages
form_data["messages"] = process_messages_with_output(form_data.get("messages", []))
@@ -2094,23 +2181,27 @@ async def process_chat_payload(request, form_data, user, metadata, model):
)
if "code_interpreter" in features and features["code_interpreter"]:
form_data["messages"] = add_or_update_user_message(
(
request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
if request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE != ""
else DEFAULT_CODE_INTERPRETER_PROMPT
),
form_data["messages"],
)
# Skip XML-tag prompt injection when native FC is enabled —
# execute_code will be injected as a builtin tool instead
if metadata.get("params", {}).get("function_calling") != "native":
form_data["messages"] = add_or_update_user_message(
(
request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
if request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE
!= ""
else DEFAULT_CODE_INTERPRETER_PROMPT
),
form_data["messages"],
)
tool_ids = form_data.pop("tool_ids", None)
files = form_data.pop("files", None)
# Skills: inject manifest only — model uses view_skill tool to load full content on-demand
user_skill_ids = form_data.pop("skill_ids", None) or []
model_skill_ids = model.get("info", {}).get("meta", {}).get("skillIds", [])
# Skills
user_skill_ids = set(form_data.pop("skill_ids", None) or [])
model_skill_ids = set(model.get("info", {}).get("meta", {}).get("skillIds", []))
all_skill_ids = list(set(user_skill_ids + model_skill_ids))
all_skill_ids = user_skill_ids | model_skill_ids
available_skills = []
if all_skill_ids:
from open_webui.models.skills import Skills as SkillsModel
@@ -2126,13 +2217,24 @@ async def process_chat_payload(request, form_data, user, metadata, model):
and s.is_active
]
if available_skills:
manifest = "<available_skills>\n"
for skill in available_skills:
manifest += f"<skill>\n<name>{skill.name}</name>\n<description>{skill.description or ''}</description>\n</skill>\n"
manifest += "</available_skills>"
skill_descriptions = ""
for skill in available_skills:
if skill.id in user_skill_ids:
# User-selected: inject full content
form_data["messages"] = add_or_update_system_message(
f'<skill name="{skill.name}">\n{skill.content}\n</skill>',
form_data["messages"],
append=True,
)
else:
# Model-attached: name+description only
skill_descriptions += f"<skill>\n<name>{skill.name}</name>\n<description>{skill.description or ''}</description>\n</skill>\n"
if skill_descriptions:
form_data["messages"] = add_or_update_system_message(
manifest, form_data["messages"], append=True
f"<available_skills>\n{skill_descriptions}</available_skills>",
form_data["messages"],
append=True,
)
prompt = get_last_user_message(form_data["messages"])
@@ -2369,7 +2471,9 @@ async def process_chat_payload(request, form_data, user, metadata, model):
{
**extra_params,
"__event_emitter__": event_emitter,
"__skill_ids__": [s.id for s in available_skills],
"__skill_ids__": [
s.id for s in available_skills if s.id not in user_skill_ids
],
},
features,
model,
@@ -2930,11 +3034,14 @@ async def streaming_chat_response_handler(response, ctx):
# Handle as a background task
async def response_handler(response, events):
def tag_output_handler(content_type, tags, content, output):
def tag_output_handler(content_type, tags, output):
"""
Detect special tags (reasoning, solution, code_interpreter) in streaming
content and create corresponding OR-aligned output items directly.
Operates on output items instead of content_blocks.
Uses the text from the output items themselves for tag detection,
eliminating state divergence between accumulated content and items.
"""
end_flag = False
@@ -2974,6 +3081,8 @@ async def streaming_chat_response_handler(response, ctx):
last_type = output[-1].get("type", "") if output else ""
if last_type == "message":
# Use the output item's own text for tag detection
item_text = get_last_text(output)
for start_tag, end_tag in tags:
start_tag_pattern = rf"{re.escape(start_tag)}"
@@ -2982,7 +3091,7 @@ async def streaming_chat_response_handler(response, ctx):
rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>"
)
match = re.search(start_tag_pattern, content)
match = re.search(start_tag_pattern, item_text)
if match:
try:
attr_content = match.group(1) if match.group(1) else ""
@@ -2991,20 +3100,13 @@ async def streaming_chat_response_handler(response, ctx):
attributes = extract_attributes(attr_content)
before_tag = content[: match.start()]
after_tag = content[match.end() :]
before_tag = item_text[: match.start()]
after_tag = item_text[match.end() :]
# Remove the start tag and everything after from last message
current_text = get_last_text(output)
set_last_text(
output,
current_text.replace(match.group(0) + after_tag, ""),
)
# Keep only text before the tag in the message
set_last_text(output, before_tag)
if before_tag:
set_last_text(output, before_tag)
if not get_last_text(output).strip():
if not before_tag.strip():
# Remove empty message item
if output and output[-1].get("type") == "message":
output.pop()
@@ -3069,9 +3171,11 @@ async def streaming_chat_response_handler(response, ctx):
else:
set_last_text(output, after_tag)
tag_output_handler(
content_type, tags, after_tag, output
_, recursive_end = tag_output_handler(
content_type, tags, output
)
if recursive_end:
end_flag = True
break
@@ -3090,25 +3194,22 @@ async def streaming_chat_response_handler(response, ctx):
start_tag = item.get("start_tag", "")
end_tag = item.get("end_tag", "")
if end_tag.startswith("<") and end_tag.endswith(">"):
end_tag_pattern = rf"{re.escape(end_tag)}"
end_tag_pattern = rf"{re.escape(end_tag)}"
# Get the block content from the item itself
if last_type == "reasoning":
parts = item.get("content", [])
block_content = ""
if parts and parts[-1].get("type") == "output_text":
block_content = parts[-1].get("text", "")
elif last_type == "open_webui:code_interpreter":
block_content = item.get("code", "")
else:
end_tag_pattern = rf"{re.escape(end_tag)}"
block_content = get_last_text(output)
if re.search(end_tag_pattern, content):
if re.search(end_tag_pattern, block_content):
end_flag = True
# Get the block content
if last_type == "reasoning":
parts = item.get("content", [])
block_content = ""
if parts and parts[-1].get("type") == "output_text":
block_content = parts[-1].get("text", "")
elif last_type == "open_webui:code_interpreter":
block_content = item.get("code", "")
else:
block_content = get_last_text(output)
# Strip start and end tags from content
start_tag_pattern = rf"{re.escape(start_tag)}"
if start_tag.startswith("<") and start_tag.endswith(">"):
@@ -3151,36 +3252,20 @@ async def streaming_chat_response_handler(response, ctx):
item["ended_at"] = time.time()
# Reset by appending a new message item for leftover
if content_type != "code_interpreter":
output.append(
{
"type": "message",
"id": output_id("msg"),
"status": "in_progress",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": leftover_content,
}
],
}
)
else:
output.append(
{
"type": "message",
"id": output_id("msg"),
"status": "in_progress",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": leftover_content,
}
],
}
)
output.append(
{
"type": "message",
"id": output_id("msg"),
"status": "in_progress",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": leftover_content,
}
],
}
)
else:
# Remove the block if content is empty
output.pop()
@@ -3199,19 +3284,7 @@ async def streaming_chat_response_handler(response, ctx):
}
)
# Clean processed content
start_tag_clean = rf"{re.escape(start_tag)}"
if start_tag.startswith("<") and start_tag.endswith(">"):
start_tag_clean = rf"<{re.escape(start_tag[1:-1])}(\s.*?)?>"
content = re.sub(
rf"{start_tag_clean}(.|\n)*?{re.escape(end_tag)}",
"",
content,
flags=re.DOTALL,
)
return content, output, end_flag
return output, end_flag
message = Chats.get_message_by_id_and_message_id(
metadata["chat_id"], metadata["message_id"]
@@ -3674,58 +3747,132 @@ async def streaming_chat_response_handler(response, ctx):
)
content = f"{content}{value}"
if (
not output
or output[-1].get("type") != "message"
):
output.append(
{
"type": "message",
"id": output_id("msg"),
"status": "in_progress",
"role": "assistant",
"content": [
# Check if we're inside a tag-based block
# (reasoning, code_interpreter, or solution).
# If so, append to the existing in-progress
# item instead of creating a new message —
# otherwise tag_output_handler re-detects the
# start tag on every chunk and fragments the
# output.
last_item = output[-1] if output else None
last_item_type = (
last_item.get("type", "")
if last_item
else ""
)
inside_tag_block = (
last_item is not None
and last_item.get("status") == "in_progress"
and last_item.get("attributes", {}).get(
"type"
)
!= "reasoning_content"
and (
last_item_type == "reasoning"
or last_item_type
== "open_webui:code_interpreter"
or (
last_item_type == "message"
and last_item.get("_tag_type")
is not None
)
)
)
if inside_tag_block:
# Append to the existing tag-based item
if (
last_item_type
== "open_webui:code_interpreter"
):
last_item["code"] = (
last_item.get("code", "") + value
)
elif last_item_type == "reasoning":
parts = last_item.get("content", [])
if (
parts
and parts[-1].get("type")
== "output_text"
):
parts[-1]["text"] += value
else:
last_item["content"] = [
{
"type": "output_text",
"text": "",
"text": value,
}
],
}
)
# Append value to last message item's text
msg_parts = output[-1].get("content", [])
if (
msg_parts
and msg_parts[-1].get("type")
== "output_text"
):
msg_parts[-1]["text"] += value
]
else:
# solution or other _tag_type message
msg_parts = last_item.get("content", [])
if (
msg_parts
and msg_parts[-1].get("type")
== "output_text"
):
msg_parts[-1]["text"] += value
else:
last_item["content"] = [
{
"type": "output_text",
"text": value,
}
]
else:
output[-1]["content"] = [
{"type": "output_text", "text": value}
]
if (
not output
or output[-1].get("type") != "message"
):
output.append(
{
"type": "message",
"id": output_id("msg"),
"status": "in_progress",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "",
}
],
}
)
# Append value to last message item's text
msg_parts = output[-1].get("content", [])
if (
msg_parts
and msg_parts[-1].get("type")
== "output_text"
):
msg_parts[-1]["text"] += value
else:
output[-1]["content"] = [
{
"type": "output_text",
"text": value,
}
]
if DETECT_REASONING_TAGS:
content, output, _ = tag_output_handler(
output, _ = tag_output_handler(
"reasoning",
reasoning_tags,
content,
output,
)
content, output, _ = tag_output_handler(
output, _ = tag_output_handler(
"solution",
DEFAULT_SOLUTION_TAGS,
content,
output,
)
if DETECT_CODE_INTERPRETER:
content, output, end = tag_output_handler(
output, end = tag_output_handler(
"code_interpreter",
DEFAULT_CODE_INTERPRETER_TAGS,
content,
output,
)
+20 -18
View File
@@ -230,25 +230,27 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]:
# else: skip reasoning blocks for normal LLM messages
elif item_type == "open_webui:code_interpreter":
if raw:
# Include code interpreter content for LLM re-processing
code = item.get("code", "")
code_output = item.get("output", "")
# Always include code interpreter content so the LLM knows
# the code was already executed and doesn't retry.
code = item.get("code", "")
code_output = item.get("output", "")
if code:
lang = item.get("lang", "python")
pending_content.append(f"```{lang}\n{code}\n```")
if code:
pending_content.append(
f"<code_interpreter>\n{code}\n</code_interpreter>"
)
if code_output:
if isinstance(code_output, dict):
stdout = code_output.get("stdout", "")
result = code_output.get("result", "")
output_text = stdout or result
else:
output_text = str(code_output)
if output_text:
pending_content.append(f"Output:\n{output_text}")
# else: skip extension types
if code_output:
if isinstance(code_output, dict):
stdout = code_output.get("stdout", "")
result = code_output.get("result", "")
output_text = stdout or result
else:
output_text = str(code_output)
if output_text:
pending_content.append(
f"<code_interpreter_output>\n{output_text}\n</code_interpreter_output>"
)
elif item_type.startswith("open_webui:"):
# Skip other extension types
@@ -445,7 +447,7 @@ def openai_chat_completion_message_template(
**({"tool_calls": tool_calls} if tool_calls else {}),
}
template["choices"][0]["finish_reason"] = "stop"
template["choices"][0]["finish_reason"] = "tool_calls" if tool_calls else "stop"
if usage:
template["usage"] = usage
+56 -26
View File
@@ -286,9 +286,26 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
}
]
def get_function_module_by_id(function_id):
function_module, _, _ = get_function_module_from_cache(request, function_id)
return function_module
# Batch-prefetch all needed function records to avoid N+1 queries
all_function_ids = set()
for model in models:
all_function_ids.update(model.get("action_ids", []))
all_function_ids.update(model.get("filter_ids", []))
all_function_ids.update(global_action_ids)
all_function_ids.update(global_filter_ids)
functions_by_id = {
f.id: f for f in Functions.get_functions_by_ids(list(all_function_ids))
}
# Pre-warm the function module cache once per unique function ID.
# This ensures each function's DB freshness check runs exactly once,
# not once per (model × function) pair.
for function_id in all_function_ids:
try:
get_function_module_from_cache(request, function_id)
except Exception as e:
log.info(f"Failed to load function module for {function_id}: {e}")
for model in models:
action_ids = [
@@ -304,23 +321,30 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
model["actions"] = []
for action_id in action_ids:
action_function = Functions.get_function_by_id(action_id)
action_function = functions_by_id.get(action_id)
if action_function is None:
raise Exception(f"Action not found: {action_id}")
log.info(f"Action not found: {action_id}")
continue
function_module = get_function_module_by_id(action_id)
function_module = request.app.state.FUNCTIONS.get(action_id)
if function_module is None:
log.info(f"Failed to load action module: {action_id}")
continue
model["actions"].extend(
get_action_items_from_module(action_function, function_module)
)
model["filters"] = []
for filter_id in filter_ids:
filter_function = Functions.get_function_by_id(filter_id)
filter_function = functions_by_id.get(filter_id)
if filter_function is None:
raise Exception(f"Filter not found: {filter_id}")
function_module = get_function_module_by_id(filter_id)
log.info(f"Filter not found: {filter_id}")
continue
function_module = request.app.state.FUNCTIONS.get(filter_id)
if function_module is None:
log.info(f"Failed to load filter module: {filter_id}")
continue
if getattr(function_module, "toggle", None):
model["filters"].extend(
get_filter_items_from_module(filter_function, function_module)
@@ -371,16 +395,29 @@ def get_filtered_models(models, user, db=None):
user.role == "user"
or (user.role == "admin" and not BYPASS_ADMIN_ACCESS_CONTROL)
) and not BYPASS_MODEL_ACCESS_CONTROL:
model_ids = [model["id"] for model in models if not model.get("arena")]
model_infos = {
model_info.id: model_info
for model_info in Models.get_models_by_ids(model_ids)
}
model_infos = {}
for model in models:
if model.get("arena"):
continue
info = model.get("info")
if info:
model_infos[model["id"]] = info
filtered_models = []
user_group_ids = {
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
}
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
user_id=user.id,
resource_type="model",
resource_ids=list(model_infos.keys()),
permission="read",
user_group_ids=user_group_ids,
db=db,
)
filtered_models = []
for model in models:
if model.get("arena"):
meta = model.get("info", {}).get("meta", {})
@@ -394,19 +431,12 @@ def get_filtered_models(models, user, db=None):
filtered_models.append(model)
continue
model_info = model_infos.get(model["id"], None)
model_info = model_infos.get(model["id"])
if model_info:
if (
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
or user.id == model_info.user_id
or AccessGrants.has_access(
user_id=user.id,
resource_type="model",
resource_id=model_info.id,
permission="read",
user_group_ids=user_group_ids,
db=db,
)
or user.id == model_info["user_id"]
or model["id"] in accessible_model_ids
):
filtered_models.append(model)
+3
View File
@@ -166,6 +166,9 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
model, message_content, reasoning_content, openai_tool_calls, usage
)
if done and openai_tool_calls:
data["choices"][0]["finish_reason"] = "tool_calls"
line = f"data: {json.dumps(data)}\n\n"
yield line
+11
View File
@@ -77,6 +77,7 @@ from open_webui.tools.builtin import (
query_knowledge_bases,
search_knowledge_files,
query_knowledge_files,
view_file,
view_knowledge_file,
view_skill,
)
@@ -445,6 +446,12 @@ def get_builtin_tools(
if model_knowledge:
# Model has attached knowledge - only allow semantic search within it
builtin_functions.append(query_knowledge_files)
knowledge_types = {item.get("type") for item in model_knowledge}
if "file" in knowledge_types or "collection" in knowledge_types:
builtin_functions.append(view_file)
if "note" in knowledge_types:
builtin_functions.append(view_note)
else:
# No model knowledge - allow full KB browsing
builtin_functions.extend(
@@ -471,6 +478,7 @@ def get_builtin_tools(
is_builtin_tool_enabled("web_search")
and getattr(request.app.state.config, "ENABLE_WEB_SEARCH", False)
and get_model_capability("web_search")
and features.get("web_search")
):
builtin_functions.extend([search_web, fetch_url])
@@ -479,12 +487,14 @@ def get_builtin_tools(
is_builtin_tool_enabled("image_generation")
and getattr(request.app.state.config, "ENABLE_IMAGE_GENERATION", False)
and get_model_capability("image_generation")
and features.get("image_generation")
):
builtin_functions.append(generate_image)
if (
is_builtin_tool_enabled("image_generation")
and getattr(request.app.state.config, "ENABLE_IMAGE_EDIT", False)
and get_model_capability("image_generation")
and features.get("image_generation")
):
builtin_functions.append(edit_image)
@@ -493,6 +503,7 @@ def get_builtin_tools(
is_builtin_tool_enabled("code_interpreter")
and getattr(request.app.state.config, "ENABLE_CODE_INTERPRETER", True)
and get_model_capability("code_interpreter")
and features.get("code_interpreter")
):
builtin_functions.append(execute_code)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.8.0",
"version": "0.8.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.8.0",
"version": "0.8.3",
"dependencies": {
"@azure/msal-browser": "^4.5.0",
"@codemirror/lang-javascript": "^6.2.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.8.0",
"version": "0.8.3",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
+4 -2
View File
@@ -241,7 +241,8 @@ export const imageEdits = async (
prompt: string,
model?: string,
size?: string,
n?: number
n?: number,
background?: string
) => {
let error = null;
@@ -258,7 +259,8 @@ export const imageEdits = async (
prompt,
...(model && { model }),
...(size && { size }),
...(n && { n })
...(n && { n }),
...(background && { background })
}
})
})
+6 -2
View File
@@ -250,8 +250,12 @@
const submitHandler = async () => {
loading = true;
// remove trailing slash from url
url = url.replace(/\/$/, '');
// remove trailing slash from url for non-MCP connections
// MCP servers may require a trailing slash; stripping it can cause
// 301 redirects that lose auth headers (see #21179)
if (type !== 'mcp') {
url = url.replace(/\/$/, '');
}
if (id.includes(':') || id.includes('|')) {
toast.error($i18n.t('ID cannot contain ":" or "|" characters'));
loading = false;
+105 -170
View File
@@ -49,188 +49,123 @@
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
saveHandler();
}}
>
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
<div class="flex flex-col h-full justify-between text-sm">
<div class="space-y-3 overflow-y-scroll scrollbar-hidden h-full">
<input
id="config-json-input"
hidden
type="file"
accept=".json"
on:change={(e) => {
const file = e.target.files[0];
const reader = new FileReader();
<input
id="config-json-input"
hidden
type="file"
accept=".json"
on:change={(e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async (e) => {
const res = await importConfig(localStorage.token, JSON.parse(e.target.result)).catch(
(error) => {
toast.error(`${error}`);
}
);
if (res) {
toast.success($i18n.t('Config imported successfully'));
reader.onload = async (e) => {
const res = await importConfig(localStorage.token, JSON.parse(e.target.result)).catch(
(error) => {
toast.error(`${error}`);
}
e.target.value = null;
};
);
reader.readAsText(file);
}}
/>
if (res) {
toast.success($i18n.t('Config imported successfully'));
}
e.target.value = null;
};
<button
type="button"
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={async () => {
document.getElementById('config-json-input').click();
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Import Config from JSON File')}
</div>
</button>
reader.readAsText(file);
}}
/>
<button
type="button"
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={async () => {
const config = await exportConfig(localStorage.token);
const blob = new Blob([JSON.stringify(config)], {
type: 'application/json'
});
saveAs(blob, `config-${Date.now()}.json`);
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Export Config to JSON File')}
</div>
</button>
<hr class="border-gray-50 dark:border-gray-850/30 my-1" />
{#if $config?.features.enable_admin_export ?? true}
<div class=" flex w-full justify-between">
<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
<div>
<div class="mb-1 text-sm font-medium">{$i18n.t('Config')}</div>
<div>
<div class="py-0.5 flex w-full justify-between">
<div class="self-center text-xs">{$i18n.t('Import Config')}</div>
<button
class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
type="button"
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
// exportAllUserChats();
downloadDatabase(localStorage.token).catch((error) => {
toast.error(`${error}`);
});
document.getElementById('config-json-input').click();
}}
type="button"
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
<span class="self-center">{$i18n.t('Import')}</span>
</button>
</div>
</div>
<button
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
exportAllUserChats();
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Export All Chats (All Users)')}
</div>
</button>
<button
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
exportUsers();
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Export Users')}
</div>
</button>
{/if}
<div>
<div class="py-0.5 flex w-full justify-between">
<div class="self-center text-xs">{$i18n.t('Export Config')}</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={async () => {
const config = await exportConfig(localStorage.token);
const blob = new Blob([JSON.stringify(config)], {
type: 'application/json'
});
saveAs(blob, `config-${Date.now()}.json`);
}}
type="button"
>
<span class="self-center">{$i18n.t('Export')}</span>
</button>
</div>
</div>
</div>
{#if $config?.features.enable_admin_export ?? true}
<div>
<div class="mb-1 text-sm font-medium">{$i18n.t('Database')}</div>
<div>
<div class="py-0.5 flex w-full justify-between">
<div class="self-center text-xs">{$i18n.t('Download Database')}</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
downloadDatabase(localStorage.token).catch((error) => {
toast.error(`${error}`);
});
}}
type="button"
>
<span class="self-center">{$i18n.t('Download')}</span>
</button>
</div>
</div>
<div>
<div class="py-0.5 flex w-full justify-between">
<div class="self-center text-xs">{$i18n.t('Export All Chats (All Users)')}</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
exportAllUserChats();
}}
type="button"
>
<span class="self-center">{$i18n.t('Export')}</span>
</button>
</div>
</div>
<div>
<div class="py-0.5 flex w-full justify-between">
<div class="self-center text-xs">{$i18n.t('Export Users')}</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
exportUsers();
}}
type="button"
>
<span class="self-center">{$i18n.t('Export')}</span>
</button>
</div>
</div>
</div>
{/if}
</div>
</form>
</div>
@@ -152,7 +152,15 @@
if (taskConfig.TASK_MODEL) {
const model = models.find((m) => m.id === taskConfig.TASK_MODEL);
if (model) {
if (model?.access_control !== null) {
if (
model?.access_grants &&
!model.access_grants.some(
(g) =>
g.principal_type === 'user' &&
g.principal_id === '*' &&
g.permission === 'read'
)
) {
toast.error(
$i18n.t(
'This model is not publicly available. Please select another model.'
@@ -187,7 +195,15 @@
if (taskConfig.TASK_MODEL_EXTERNAL) {
const model = models.find((m) => m.id === taskConfig.TASK_MODEL_EXTERNAL);
if (model) {
if (model?.access_control !== null) {
if (
model?.access_grants &&
!model.access_grants.some(
(g) =>
g.principal_type === 'user' &&
g.principal_id === '*' &&
g.permission === 'read'
)
) {
toast.error(
$i18n.t(
'This model is not publicly available. Please select another model.'
@@ -160,7 +160,7 @@
name: model.name,
base_model_id: null,
params: {},
access_control: {},
access_grants: [],
...model
}).catch((error) => {
return null;
@@ -188,7 +188,7 @@
base_model_id: null,
meta: {},
params: {},
access_control: {},
access_grants: [],
is_active: model.is_active
}).catch((error) => {
return null;
@@ -324,6 +324,40 @@
</div>
{/if}
<div class="flex flex-col w-full">
<div class="flex w-full justify-between my-1">
<div class=" self-center text-xs font-medium">
{$i18n.t('Skills Sharing')}
</div>
<Switch bind:state={permissions.sharing.skills} />
</div>
{#if defaultPermissions?.sharing?.skills && !permissions.sharing.skills}
<div>
<div class="text-xs text-gray-500">
{$i18n.t('This is a default user permission and will remain enabled.')}
</div>
</div>
{/if}
</div>
{#if permissions.sharing.skills}
<div class="flex flex-col w-full">
<div class="flex w-full justify-between my-1">
<div class=" self-center text-xs font-medium">
{$i18n.t('Skills Public Sharing')}
</div>
<Switch bind:state={permissions.sharing.public_skills} />
</div>
{#if defaultPermissions?.sharing?.public_skills && !permissions.sharing.public_skills}
<div>
<div class="text-xs text-gray-500">
{$i18n.t('This is a default user permission and will remain enabled.')}
</div>
</div>
{/if}
</div>
{/if}
<div class="flex flex-col w-full">
<div class="flex w-full justify-between my-1">
<div class=" self-center text-xs font-medium">
+5 -1
View File
@@ -2085,7 +2085,11 @@
),
features: getFeatures(),
variables: {
...getPromptVariables($user?.name, $settings?.userLocation ? userLocation : undefined)
...getPromptVariables(
$user?.name,
$settings?.userLocation ? userLocation : undefined,
$user?.email
)
},
model_item: $models.find((m) => m.id === model.id),
@@ -186,12 +186,16 @@
}}
data-selected={idx === selectedIdx}
>
<div class=" text-black dark:text-gray-100 flex items-center gap-1 shrink-0">
<div class="w-full text-left text-black dark:text-gray-100 flex items-center gap-1">
<Tooltip content={$i18n.t('Collection')} placement="top">
<Database className="size-4" />
</Tooltip>
<Tooltip content={item.description || decodeString(item?.name)} placement="top-start">
<Tooltip
content={item.description || decodeString(item?.name)}
placement="top-start"
className="flex flex-1 min-w-0"
>
<div class="line-clamp-1 flex-1 text-sm">
{decodeString(item?.name)}
</div>
@@ -2,6 +2,7 @@
import { getContext, onMount, tick } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings } from '$lib/stores';
@@ -10,6 +11,9 @@
const i18n = getContext('i18n');
const CONTENT_PREVIEW_LIMIT = 10000;
let expandedDocs: Set<number> = new Set();
export let show = false;
export let citation;
export let showPercentage = false;
@@ -35,6 +39,7 @@
}
$: if (citation) {
expandedDocs = new Set();
mergedDocuments = citation.document?.map((c, i) => {
return {
source: citation.source,
@@ -216,9 +221,36 @@
title={$i18n.t('Content')}
></iframe>
{:else}
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">{document.document
.trim()
.replace(/\n\n+/g, '\n\n')}</pre>
{@const rawContent = document.document.trim().replace(/\n\n+/g, '\n\n')}
{@const isTruncated =
($settings?.renderMarkdownInPreviews ?? true) &&
rawContent.length > CONTENT_PREVIEW_LIMIT &&
!expandedDocs.has(documentIdx)}
{#if $settings?.renderMarkdownInPreviews ?? true}
<div class="text-sm prose dark:prose-invert max-w-full">
<Markdown
content={isTruncated
? rawContent.slice(0, CONTENT_PREVIEW_LIMIT)
: rawContent}
id="citation-{documentIdx}"
/>
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => {
expandedDocs.add(documentIdx);
expandedDocs = expandedDocs;
}}
>
{$i18n.t('Show all ({{COUNT}} characters)', {
COUNT: rawContent.length.toLocaleString()
})}
</button>
{/if}
{:else}
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">{rawContent}</pre>
{/if}
{/if}
</div>
</div>
@@ -417,13 +417,13 @@
<div>
<div
class="relative {className} flex flex-col rounded-3xl border border-gray-100/30 dark:border-gray-850/30 my-0.5"
class="relative {className} flex flex-col rounded-2xl border border-gray-100/30 dark:border-gray-850/30 my-0.5"
dir="ltr"
>
{#if ['mermaid', 'vega', 'vega-lite'].includes(lang)}
{#if renderHTML}
<SvgPanZoom
className=" rounded-3xl max-h-fit overflow-hidden"
className=" rounded-2xl max-h-fit overflow-hidden"
svg={renderHTML}
content={_token.text}
/>
@@ -441,13 +441,13 @@
{/if}
{:else}
<div
class="absolute left-0 right-0 py-2.5 pr-3 text-text-300 pl-4.5 text-xs font-medium dark:text-white"
class="absolute left-0 right-0 py-1.5 pr-3 text-text-300 pl-4.5 text-xs font-medium dark:text-white"
>
{lang}
</div>
<div
class="sticky {stickyButtonsClassName} left-0 right-0 py-2 pr-3 flex items-center justify-end w-full z-10 text-xs text-black dark:text-white"
class="sticky {stickyButtonsClassName} left-0 right-0 py-1.5 pr-3 flex items-center justify-end w-full z-10 text-xs text-black dark:text-white"
>
<div class="flex items-center gap-0.5">
<button
@@ -514,13 +514,13 @@
</div>
<div
class="language-{lang} rounded-t-3xl -mt-9 {editorClassName
class="language-{lang} rounded-t-2xl -mt-8 {editorClassName
? editorClassName
: executing || stdout || stderr || result
? ''
: 'rounded-b-3xl'} overflow-hidden"
: 'rounded-b-2xl'} overflow-hidden"
>
<div class=" pt-8 bg-white dark:bg-black"></div>
<div class=" pt-6.5 bg-white dark:bg-black"></div>
{#if !collapsed}
{#if edit}
@@ -550,7 +550,7 @@
{/if}
{:else}
<div
class="bg-white dark:bg-black dark:text-white rounded-b-3xl! pt-0.5 pb-3 px-4 flex flex-col gap-2 text-xs"
class="bg-white dark:bg-black dark:text-white rounded-b-2xl! pt-0.5 pb-2 px-4 flex flex-col gap-2 text-xs"
>
<span class="text-gray-500 italic">
{$i18n.t('{{COUNT}} hidden lines', {
@@ -569,7 +569,7 @@
{#if executing || stdout || stderr || result || files}
<div
class="bg-gray-50 dark:bg-black dark:text-white rounded-b-3xl! py-4 px-4 flex flex-col gap-2"
class="bg-gray-50 dark:bg-black dark:text-white rounded-b-2xl! py-4 px-4 flex flex-col gap-2"
>
{#if executing}
<div class=" ">
@@ -18,6 +18,7 @@
import TextToken from './MarkdownInlineTokens/TextToken.svelte';
import CodespanToken from './MarkdownInlineTokens/CodespanToken.svelte';
import MentionToken from './MarkdownInlineTokens/MentionToken.svelte';
import NoteLinkToken from './MarkdownInlineTokens/NoteLinkToken.svelte';
import SourceToken from './SourceToken.svelte';
export let id: string;
@@ -26,6 +27,24 @@
export let sourceIds = [];
export let onSourceClick: Function = () => {};
/**
* Check if a URL is a same-origin note link and return the note ID if so.
*/
const getNoteIdFromHref = (href: string): string | null => {
try {
const url = new URL(href, window.location.origin);
if (url.origin === window.location.origin) {
const match = url.pathname.match(/^\/notes\/([^/]+)$/);
if (match) {
return match[1];
}
}
} catch {
// Invalid URL
}
return null;
};
/**
* Handle link clicks - intercept same-origin app URLs for in-app navigation
*/
@@ -54,7 +73,10 @@
{:else if token.type === 'html'}
<HtmlToken {id} {token} {onSourceClick} />
{:else if token.type === 'link'}
{#if token.tokens}
{@const noteId = getNoteIdFromHref(token.href)}
{#if noteId}
<NoteLinkToken {noteId} href={token.href} />
{:else if token.tokens}
<a
href={token.href}
target="_blank"
@@ -0,0 +1,73 @@
<script lang="ts">
import { onMount, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { getNoteById } from '$lib/apis/notes';
import { getUserInfoById } from '$lib/apis/users';
import { capitalizeFirstLetter } from '$lib/utils';
const i18n = getContext('i18n');
export let noteId: string;
export let href: string;
let title = '';
let author = '';
let loading = true;
onMount(async () => {
try {
const note = await getNoteById(localStorage.token, noteId);
if (note) {
title = note.title || $i18n.t('Untitled');
if (note.user_id) {
try {
const userInfo = await getUserInfoById(localStorage.token, note.user_id);
if (userInfo) {
author = capitalizeFirstLetter(userInfo.name ?? userInfo.email ?? '');
}
} catch {
// user lookup failed, skip author
}
}
}
} catch {
title = $i18n.t('Note');
} finally {
loading = false;
}
});
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<button
class="relative group py-2 px-3 w-60 flex flex-col bg-white dark:bg-gray-850 border border-gray-50/30 dark:border-gray-800/30 rounded-xl text-left cursor-pointer"
type="button"
on:click|preventDefault|stopPropagation={() => {
try {
const url = new URL(href, window.location.origin);
goto(url.pathname);
} catch {
// fallback
}
}}
>
<div class="flex flex-col justify-center w-full min-w-0">
<div class="dark:text-gray-100 text-sm flex justify-between items-center gap-2">
<div class="font-medium line-clamp-1 flex-1 min-w-0">
{#if loading}
<span class="text-gray-400">...</span>
{:else}
{title}
{/if}
</div>
<div class="text-gray-500 text-xs shrink-0">{$i18n.t('Note')}</div>
</div>
{#if author}
<div class="text-gray-500 text-xs line-clamp-1 mt-0.5">
{$i18n.t('By {{name}}', { name: author })}
</div>
{/if}
</div>
</button>
@@ -365,8 +365,13 @@
await tick();
const messagesContainer = document.getElementById('messages-container');
const savedScrollTop = messagesContainer?.scrollTop;
editTextAreaElement.style.height = '';
editTextAreaElement.style.height = `${editTextAreaElement.scrollHeight}px`;
if (messagesContainer) messagesContainer.scrollTop = savedScrollTop;
};
const editMessageConfirmHandler = async () => {
@@ -697,8 +702,13 @@
class=" bg-transparent outline-hidden w-full resize-none"
bind:value={editedContent}
on:input={(e) => {
const messagesContainer = document.getElementById('messages-container');
const savedScrollTop = messagesContainer?.scrollTop;
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
if (messagesContainer) messagesContainer.scrollTop = savedScrollTop;
}}
on:keydown={(e) => {
if (e.key === 'Escape') {
@@ -50,6 +50,7 @@
let editedFiles = [];
let messageEditTextAreaElement: HTMLTextAreaElement;
let editScrollContainer: HTMLDivElement;
let message = JSON.parse(JSON.stringify(history.messages[messageId]));
$: if (history.messages) {
@@ -73,10 +74,14 @@
await tick();
if (messageEditTextAreaElement) {
const messagesContainer = document.getElementById('messages-container');
const savedScrollTop = messagesContainer?.scrollTop;
messageEditTextAreaElement.style.height = '';
messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`;
messageEditTextAreaElement?.focus();
if (messagesContainer) messagesContainer.scrollTop = savedScrollTop;
messageEditTextAreaElement?.focus({ preventScroll: true });
}
};
@@ -283,15 +288,22 @@
</div>
{/if}
<div class="max-h-96 overflow-auto">
<div class="max-h-96 overflow-auto" bind:this={editScrollContainer}>
<textarea
id="message-edit-{message.id}"
bind:this={messageEditTextAreaElement}
class=" bg-transparent outline-hidden w-full resize-none"
bind:value={editedContent}
on:input={(e) => {
const messagesContainer = document.getElementById('messages-container');
const savedScrollTop = messagesContainer?.scrollTop;
const savedInnerScroll = editScrollContainer?.scrollTop;
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
if (messagesContainer) messagesContainer.scrollTop = savedScrollTop;
if (editScrollContainer) editScrollContainer.scrollTop = savedInnerScroll;
}}
on:keydown={(e) => {
if (e.key === 'Escape') {
@@ -3,13 +3,15 @@
import { flyAndScale } from '$lib/utils/transitions';
import { getContext } from 'svelte';
import { goto } from '$app/navigation';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Pin from '$lib/components/icons/Pin.svelte';
import PinSlash from '$lib/components/icons/PinSlash.svelte';
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
import Link from '$lib/components/icons/Link.svelte';
import { config, settings } from '$lib/stores';
import Pencil from '$lib/components/icons/Pencil.svelte';
import { config, settings, user } from '$lib/stores';
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
const i18n = getContext('i18n');
@@ -52,6 +54,30 @@
align="end"
transition={flyAndScale}
>
{#if model?.preset || model?.info?.base_model_id ? model?.info?.user_id === $user?.id : $user?.role === 'admin'}
<DropdownMenu.Item
type="button"
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2"
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
goto(
model?.preset || model?.info?.base_model_id
? `/workspace/models/edit?id=${encodeURIComponent(model?.id ?? '')}`
: `/admin/settings/models?id=${encodeURIComponent(model?.id ?? '')}`
);
show = false;
}}
>
<Pencil className="size-4" />
<div class="flex items-center">{$i18n.t('Edit')}</div>
</DropdownMenu.Item>
<hr class="border-gray-50 dark:border-gray-800/30 my-1" />
{/if}
<DropdownMenu.Item
type="button"
aria-pressed={($settings?.pinnedModels ?? []).includes(model?.id)}
@@ -96,7 +122,7 @@
</DropdownMenu.Item>
{#if $config?.features.enable_community_sharing}
<hr class="border-gray-50 dark:border-gray-850/30 my-1" />
<hr class="border-gray-50 dark:border-gray-800/30 my-1" />
<DropdownMenu.Item
type="button"
@@ -178,6 +178,16 @@
selectedModelIdx = 0;
}
// Set the virtual scroll position so the selected item is rendered and centered
const targetScrollTop = Math.max(0, selectedModelIdx * ITEM_HEIGHT - 128 + ITEM_HEIGHT / 2);
listScrollTop = targetScrollTop;
await tick();
if (listContainer) {
listContainer.scrollTop = targetScrollTop;
}
await tick();
const item = document.querySelector(`[data-arrow-selected="true"]`);
item?.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
@@ -379,6 +389,7 @@
bind:open={show}
onOpenChange={async () => {
searchValue = '';
listScrollTop = 0;
window.setTimeout(() => document.getElementById('model-search-input')?.focus(), 0);
resetView();
@@ -70,6 +70,7 @@
let chatFadeStreamingText = true;
let collapseCodeBlocks = false;
let expandDetails = false;
let renderMarkdownInPreviews = true;
let showChatTitleInTab = true;
let showFloatingActionButtons = true;
@@ -232,6 +233,7 @@
collapseCodeBlocks = $settings?.collapseCodeBlocks ?? false;
expandDetails = $settings?.expandDetails ?? false;
renderMarkdownInPreviews = $settings?.renderMarkdownInPreviews ?? true;
landingPageMode = $settings?.landingPageMode ?? '';
chatBubble = $settings?.chatBubble ?? true;
@@ -964,6 +966,25 @@
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="render-markdown-in-previews-label" class=" self-center text-xs">
{$i18n.t('Render Markdown in Previews')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="render-markdown-in-previews-label"
tooltip={true}
bind:state={renderMarkdownInPreviews}
on:change={() => {
saveSettings({ renderMarkdownInPreviews });
}}
/>
</div>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="keep-followup-prompts-label" class=" self-center text-xs">
+90 -7
View File
@@ -6,6 +6,7 @@
import { formatFileSize, getLineCount } from '$lib/utils';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings } from '$lib/stores';
import { getKnowledgeById } from '$lib/apis/knowledge';
import { getFileById, getFileContentById } from '$lib/apis/files';
@@ -14,6 +15,9 @@
const i18n = getContext('i18n');
const CONTENT_PREVIEW_LIMIT = 10000;
let expandedContent = false;
import Modal from './Modal.svelte';
import XMark from '../icons/XMark.svelte';
import Switch from './Switch.svelte';
@@ -30,6 +34,7 @@
let isPDF = false;
let isAudio = false;
let isImage = false;
let isExcel = false;
let selectedTab = '';
@@ -79,6 +84,18 @@
(item?.name && item?.name.toLowerCase().endsWith('.m4a')) ||
(item?.name && item?.name.toLowerCase().endsWith('.webm'));
$: isImage =
(item?.meta?.content_type ?? '').startsWith('image/') ||
(item?.name &&
(item.name.toLowerCase().endsWith('.png') ||
item.name.toLowerCase().endsWith('.jpg') ||
item.name.toLowerCase().endsWith('.jpeg') ||
item.name.toLowerCase().endsWith('.gif') ||
item.name.toLowerCase().endsWith('.webp') ||
item.name.toLowerCase().endsWith('.svg') ||
item.name.toLowerCase().endsWith('.bmp') ||
item.name.toLowerCase().endsWith('.ico')));
$: isExcel =
item?.meta?.content_type === 'application/vnd.ms-excel' ||
item?.meta?.content_type ===
@@ -134,6 +151,7 @@
const loadContent = async () => {
selectedTab = '';
expandedContent = false;
if (item?.type === 'collection') {
loading = true;
@@ -341,15 +359,80 @@
</div>
{/if}
{#if selectedTab === ''}
{#if isImage}
<div class="w-full max-h-[70vh] overflow-auto">
<img
src={`${WEBUI_API_BASE_URL}/files/${item.id}/content`}
alt={item?.name ?? 'Image'}
class="w-full object-contain rounded-lg"
loading="lazy"
/>
</div>
{:else if selectedTab === ''}
{#if item?.file?.data}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{(item?.file?.data?.content ?? '').trim() || 'No content'}
</div>
{@const rawContent = (item?.file?.data?.content ?? '').trim() || 'No content'}
{@const isTruncated =
($settings?.renderMarkdownInPreviews ?? true) &&
rawContent.length > CONTENT_PREVIEW_LIMIT &&
!expandedContent}
{#if $settings?.renderMarkdownInPreviews ?? true}
<div
class="max-h-96 overflow-scroll scrollbar-hidden text-sm prose dark:prose-invert max-w-full"
>
<Markdown
content={isTruncated ? rawContent.slice(0, CONTENT_PREVIEW_LIMIT) : rawContent}
id="file-preview"
/>
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => {
expandedContent = true;
}}
>
{$i18n.t('Show all ({{COUNT}} characters)', {
COUNT: rawContent.length.toLocaleString()
})}
</button>
{/if}
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{rawContent}
</div>
{/if}
{:else if item?.content}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{(item?.content ?? '').trim() || 'No content'}
</div>
{@const rawContent = (item?.content ?? '').trim() || 'No content'}
{@const isTruncated =
($settings?.renderMarkdownInPreviews ?? true) &&
rawContent.length > CONTENT_PREVIEW_LIMIT &&
!expandedContent}
{#if $settings?.renderMarkdownInPreviews ?? true}
<div
class="max-h-96 overflow-scroll scrollbar-hidden text-sm prose dark:prose-invert max-w-full"
>
<Markdown
content={isTruncated ? rawContent.slice(0, CONTENT_PREVIEW_LIMIT) : rawContent}
id="file-preview-content"
/>
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => {
expandedContent = true;
}}
>
{$i18n.t('Show all ({{COUNT}} characters)', {
COUNT: rawContent.length.toLocaleString()
})}
</button>
{/if}
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{rawContent}
</div>
{/if}
{/if}
{:else if selectedTab === 'preview'}
{#if isAudio}
@@ -691,7 +691,12 @@
element: element,
extensions: [
StarterKit.configure({
link: link
link: link,
// When rich text is off, disable Strike from StarterKit so we can
// re-add it below without its Mod-Shift-s shortcut (which conflicts
// with the Toggle Sidebar shortcut). When rich text is on, the user
// can undo strikethrough via the toolbar, so the shortcut is fine.
...(richText ? {} : { strike: false })
}),
...(dragHandle ? [ListItemDragHandle] : []),
Placeholder.configure({ placeholder: () => _placeholder, showOnlyWhenEditable: false }),
+104 -43
View File
@@ -12,6 +12,8 @@
import ChevronDown from '../icons/ChevronDown.svelte';
import Spinner from './Spinner.svelte';
import Markdown from '../chat/Messages/Markdown.svelte';
import WrenchSolid from '../icons/WrenchSolid.svelte';
import CheckCircle from '../icons/CheckCircle.svelte';
import Image from './Image.svelte';
import FullHeightIframe from './FullHeightIframe.svelte';
@@ -45,26 +47,36 @@
function formatJSONString(str: string) {
try {
const parsed = parseJSONString(str);
// If parsed is an object/array, then it's valid JSON
if (typeof parsed === 'object') {
return JSON.stringify(parsed, null, 2);
} else {
// It's a primitive value like a number, boolean, etc.
return `${JSON.stringify(String(parsed))}`;
}
} catch (e) {
// Not valid JSON, return as-is
return str;
}
}
// Decode and parse attributes
function parseArguments(str: string): Record<string, unknown> | null {
try {
const parsed = parseJSONString(str);
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
return parsed as Record<string, unknown>;
}
return null;
} catch {
return null;
}
}
$: args = decode(attributes?.arguments ?? '');
$: result = decode(attributes?.result ?? '');
$: files = parseJSONString(decode(attributes?.files ?? ''));
$: embeds = parseJSONString(decode(attributes?.embeds ?? ''));
$: isDone = attributes?.done === 'true';
$: isExecuting = attributes?.done && attributes?.done !== 'true';
$: parsedArgs = parseArguments(args);
</script>
<div {id} class={className}>
@@ -72,11 +84,8 @@
<!-- Embed Mode: Show iframes without collapsible behavior -->
<div class="py-1 w-full cursor-pointer">
<div class="w-full text-xs text-gray-500">
<div class="">
{attributes.name}
</div>
{attributes.name}
</div>
{#each embeds as embed, idx}
<div class="my-2" id={`${componentId}-tool-call-embed-${idx}`}>
<FullHeightIframe
@@ -91,7 +100,8 @@
{/each}
</div>
{:else}
<!-- Standard collapsible tool call display -->
<!-- Tool call display -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="{buttonClassName} cursor-pointer"
on:pointerup={() => {
@@ -99,35 +109,51 @@
}}
>
<div
class="w-full font-medium flex items-center justify-between gap-2 {isExecuting
class="w-full max-w-full font-medium flex items-center gap-1.5 {isExecuting
? 'shimmer'
: ''}"
>
<!-- Status icon -->
{#if isExecuting}
<div>
<Spinner className="size-4" />
</div>
{:else if isDone}
<div class="text-emerald-500 dark:text-emerald-400">
<CheckCircle className="size-4" strokeWidth="2" />
</div>
{:else}
<div class="text-gray-400 dark:text-gray-500">
<WrenchSolid className="size-3.5" />
</div>
{/if}
<div class="">
{#if isDone}
<Markdown
id={`${componentId}-tool-call-title`}
content={$i18n.t('View Result from **{{NAME}}**', {
NAME: attributes.name
})}
/>
{:else}
<Markdown
id={`${componentId}-tool-call-executing`}
content={$i18n.t('Executing **{{NAME}}**...', {
NAME: attributes.name
})}
/>
{/if}
<!-- Label -->
<div class="flex-1 line-clamp-1">
<!-- Short label (below md) -->
<span class="@md:hidden font-semibold text-black dark:text-white">{attributes.name}</span>
<!-- Full label (md and above) -->
<span class="hidden @md:inline">
{#if isDone}
<Markdown
id={`${componentId}-tool-call-title`}
content={$i18n.t('View Result from **{{NAME}}**', {
NAME: attributes.name
})}
/>
{:else}
<Markdown
id={`${componentId}-tool-call-executing`}
content={$i18n.t('Executing **{{NAME}}**...', {
NAME: attributes.name
})}
/>
{/if}
</span>
</div>
<div class="flex self-center translate-y-[1px]">
<!-- Chevron -->
<div class="flex shrink-0 self-center translate-y-[1px]">
{#if open}
<ChevronUp strokeWidth="3.5" className="size-3.5" />
{:else}
@@ -139,22 +165,57 @@
{#if open}
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
{#if isDone}
<Markdown
id={`${componentId}-tool-call-result`}
content={`> \`\`\`json
> ${formatJSONString(args)}
> ${formatJSONString(result)}
> \`\`\``}
/>
{:else}
<Markdown
id={`${componentId}-tool-call-args`}
content={`> \`\`\`json
> ${formatJSONString(args)}
> \`\`\``}
/>
{/if}
<div class="border border-gray-50 dark:border-gray-850/30 rounded-2xl my-1.5 p-3 space-y-3">
<!-- Input -->
{#if args}
<div>
<div
class="text-[10px] uppercase tracking-wider font-medium text-gray-400 dark:text-gray-500 mb-1.5 px-1"
>
{$i18n.t('Input')}
</div>
{#if parsedArgs}
<div class="px-1 space-y-0.5">
{#each Object.entries(parsedArgs) as [key, value]}
<div class="flex gap-2 text-xs py-0.5">
<span class="font-medium text-gray-600 dark:text-gray-400 shrink-0"
>{key}</span
>
<span class="text-gray-800 dark:text-gray-200 break-all"
>{typeof value === 'object' ? JSON.stringify(value) : value}</span
>
</div>
{/each}
</div>
{:else}
<div class="tool-call-body w-full max-w-none!">
<Markdown
id={`${componentId}-tool-call-args`}
content={`\`\`\`json\n${formatJSONString(args)}\n\`\`\``}
/>
</div>
{/if}
</div>
{/if}
<!-- Output -->
{#if isDone && result}
<div>
<div
class="text-[10px] uppercase tracking-wider font-medium text-gray-400 dark:text-gray-500 mb-1.5 px-1"
>
{$i18n.t('Output')}
</div>
<div class="w-full max-w-none!">
<Markdown
id={`${componentId}-tool-call-result`}
content={`\`\`\`json\n${formatJSONString(result)}\n\`\`\``}
/>
</div>
</div>
{/if}
</div>
</div>
{/if}
{/if}
@@ -9,6 +9,7 @@
import { chatCompletion } from '$lib/apis/openai';
import { splitStream } from '$lib/utils';
import Spinner from '$lib/components/common/Spinner.svelte';
import Selector from '$lib/components/chat/ModelSelector/Selector.svelte';
const i18n = getContext('i18n');
@@ -160,25 +161,28 @@
</div>
<div class="pb-3 flex justify-end">
{#if !loading}
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
on:click={() => {
submitHandler();
}}
>
{$i18n.t('Run')}
</button>
{:else}
<button
class="px-3 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-full"
on:click={() => {
stopResponse();
}}
>
{$i18n.t('Cancel')}
</button>
{/if}
<div class="flex gap-2 shrink-0">
{#if !loading}
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-lg"
on:click={() => {
submitHandler();
}}
>
{$i18n.t('Run')}
</button>
{:else}
<button
class="px-3.5 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-lg flex items-center gap-2"
on:click={() => {
stopResponse();
}}
>
<Spinner className="size-4" />
{$i18n.t('Cancel')}
</button>
{/if}
</div>
</div>
</div>
</div>
@@ -836,9 +836,7 @@
bind:show={showAccessControlModal}
bind:accessGrants={knowledge.access_grants}
share={$user?.permissions?.sharing?.knowledge || $user?.role === 'admin'}
sharePublic={$user?.permissions?.sharing?.public_knowledge ||
$user?.role === 'admin' ||
knowledge?.write_access}
sharePublic={$user?.permissions?.sharing?.public_knowledge || $user?.role === 'admin'}
onChange={async () => {
try {
await updateKnowledgeAccessGrants(localStorage.token, id, knowledge.access_grants ?? []);
@@ -856,7 +854,7 @@
<div class="w-full flex justify-between items-center">
<input
type="text"
class="text-left w-full font-medium text-lg font-primary bg-transparent outline-hidden flex-1"
class="text-left w-full text-lg bg-transparent outline-hidden flex-1"
bind:value={knowledge.name}
placeholder={$i18n.t('Knowledge Name')}
disabled={!knowledge?.write_access}
@@ -170,15 +170,27 @@
>
<div class=" text-black dark:text-gray-100 flex items-center gap-1 shrink-0">
{#if item.type === 'note'}
<Tooltip content={$i18n.t('Note')} placement="top">
<Tooltip
content={$i18n.t('Note')}
placement="top"
tippyOptions={{ zIndex: 100000 }}
>
<PageEdit className="size-4" />
</Tooltip>
{:else if item.type === 'collection'}
<Tooltip content={$i18n.t('Collection')} placement="top">
<Tooltip
content={$i18n.t('Collection')}
placement="top"
tippyOptions={{ zIndex: 100000 }}
>
<Database className="size-4" />
</Tooltip>
{:else if item.type === 'file'}
<Tooltip content={$i18n.t('File')} placement="top">
<Tooltip
content={$i18n.t('File')}
placement="top"
tippyOptions={{ zIndex: 100000 }}
>
<DocumentPage className="size-4" />
</Tooltip>
{/if}
@@ -186,6 +198,7 @@
<Tooltip
content={item.description || decodeString(item?.name)}
placement="top-start"
tippyOptions={{ zIndex: 100000 }}
>
<div class="line-clamp-1 flex-1 text-sm text-left">
{decodeString(item?.name)}
@@ -342,14 +342,14 @@
bind:accessGrants
accessRoles={preset ? ['read', 'write'] : ['read']}
share={$user?.permissions?.sharing?.models || $user?.role === 'admin'}
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin' || edit}
sharePublic={$user?.permissions?.sharing?.public_models || $user?.role === 'admin'}
onChange={async () => {
if (edit && model?.id) {
try {
await updateModelAccessGrants(localStorage.token, model.id, accessGrants);
toast.success($i18n.t('Saved'));
} catch (error) {
toast.error(`${error}`);
toast.error(error?.detail ?? `${error}`);
}
}
}}
@@ -466,11 +466,11 @@
}}
>
<div class="w-full px-1">
<div class="flex flex-col md:flex-row gap-4 w-full">
<div class="self-center md:self-start flex justify-center my-2 shrink-0">
<div class="flex flex-row gap-4 md:gap-6 w-full">
<div class="self-start flex justify-center my-2 shrink-0">
<div class="self-center">
<button
class="rounded-xl flex shrink-0 items-center {info.meta.profile_image_url !==
class="rounded-2xl flex shrink-0 items-center {info.meta.profile_image_url !==
`${WEBUI_BASE_URL}/static/favicon.png`
? 'bg-transparent'
: 'bg-white'} shadow-xl group relative"
@@ -483,13 +483,13 @@
<img
src={info.meta.profile_image_url}
alt="model profile"
class="rounded-xl size-60 object-cover shrink-0"
class="rounded-xl size-20 md:size-48 object-cover shrink-0"
/>
{:else}
<img
src="{WEBUI_BASE_URL}/static/favicon.png"
alt="model profile"
class=" rounded-xl size-60 object-cover shrink-0"
class=" rounded-xl size-20 md:size-48 object-cover shrink-0"
/>
{/if}
@@ -538,7 +538,7 @@
<div class=" flex flex-col w-full">
<div class="flex-1 w-full">
<input
class="text-4xl font-medium w-full bg-transparent outline-hidden"
class="text-3xl w-full bg-transparent outline-hidden"
placeholder={$i18n.t('Model Name')}
bind:value={name}
required
@@ -282,7 +282,7 @@
bind:accessGrants
accessRoles={['read', 'write']}
share={$user?.permissions?.sharing?.prompts || $user?.role === 'admin'}
sharePublic={$user?.permissions?.sharing?.public_prompts || $user?.role === 'admin' || edit}
sharePublic={$user?.permissions?.sharing?.public_prompts || $user?.role === 'admin'}
onChange={async () => {
if (edit && prompt?.id) {
try {
@@ -375,7 +375,7 @@
<div class="flex items-start justify-between gap-4 shrink-0">
<div class="min-w-0 flex-1">
<input
class="text-2xl font-medium w-full bg-transparent outline-hidden"
class="text-2xl w-full bg-transparent outline-hidden"
placeholder={$i18n.t('Prompt Name')}
bind:value={name}
on:input={debouncedSaveMetadata}
@@ -534,7 +534,7 @@
<div class="flex flex-col w-full">
<div class="flex items-center">
<input
class="text-2xl font-medium w-full bg-transparent outline-hidden"
class="text-2xl w-full bg-transparent outline-hidden"
placeholder={$i18n.t('Name')}
bind:value={name}
required
@@ -113,7 +113,7 @@
bind:accessGrants
accessRoles={['read', 'write']}
share={$user?.permissions?.sharing?.skills || $user?.role === 'admin'}
sharePublic={$user?.permissions?.sharing?.public_skills || $user?.role === 'admin' || edit}
sharePublic={$user?.permissions?.sharing?.public_skills || $user?.role === 'admin'}
onChange={async () => {
if (edit && skill?.id) {
try {
@@ -149,7 +149,7 @@
<div class="flex-1">
<Tooltip content={$i18n.t('e.g. Code Review Guidelines')} placement="top-start">
<input
class="w-full text-2xl font-medium bg-transparent outline-hidden font-primary"
class="w-full text-2xl bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Skill Name')}
bind:value={name}
@@ -192,7 +192,7 @@ class Tools:
bind:accessGrants
accessRoles={['read', 'write']}
share={$user?.permissions?.sharing?.tools || $user?.role === 'admin'}
sharePublic={$user?.permissions?.sharing?.public_tools || $user?.role === 'admin' || edit}
sharePublic={$user?.permissions?.sharing?.public_tools || $user?.role === 'admin'}
onChange={async () => {
if (edit && id) {
try {
@@ -238,7 +238,7 @@ class Tools:
<div class="flex-1">
<Tooltip content={$i18n.t('e.g. My Tools')} placement="top-start">
<input
class="w-full text-2xl font-medium bg-transparent outline-hidden font-primary"
class="w-full text-2xl bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Tool Name')}
bind:value={name}
+2
View File
@@ -21,6 +21,8 @@ export const DEFAULT_PERMISSIONS = {
public_prompts: false,
tools: false,
public_tools: false,
skills: false,
public_skills: false,
notes: false,
public_notes: false
},
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "يستطيع حذف المحادثات",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "الطلبات المتزامنة",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "تصدير جميع الدردشات (جميع المستخدمين)",
"Export chat (.json)": "",
"Export Chats": "تصدير جميع الدردشات",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "استيراد الدردشات",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "آخر",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "حذف الموديل",
"Rename": "إعادة تسمية",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1642,6 +1644,7 @@
"Show": "عرض",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1669,6 +1672,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "السماح بوسائل التحكم في المحادثة",
"Allow Chat Delete": "السماح بحذف المحادثة",
"Allow Chat Deletion": "السماح بحذف المحادثة",
"Allow Chat Edit": "السماح بتعديل المحادثة",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "الإكمالات",
"Compress Images in Channels": "",
"Concurrent Requests": "الطلبات المتزامنة",
"Config": "",
"Config imported successfully": "",
"Configure": "تكوين",
"Confirm": "تأكيد",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "تصدير جميع الدردشات (جميع المستخدمين)",
"Export chat (.json)": "تصدير المحادثة (.json)",
"Export Chats": "تصدير جميع الدردشات",
"Export Config to JSON File": "تصدير الإعدادات إلى ملف JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "تصدير الإعدادات المسبقة",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "استيراد الدردشات",
"Import Config from JSON File": "استيراد الإعدادات من ملف JSON",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "استيراد الإعدادات المسبقة",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "تنظيم المستخدمين الخاصين بك",
"Other": "آخر",
"Output": "",
"OUTPUT": "الإخراج",
"Output format": "تنسيق الإخراج",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "حذف الموديل",
"Rename": "إعادة تسمية",
"Render Markdown in Previews": "",
"Reorder Models": "إعادة ترتيب النماذج",
"Reply": "",
"Reply in Thread": "الرد داخل سلسلة الرسائل",
@@ -1642,6 +1644,7 @@
"Show": "عرض",
"Show \"What's New\" modal on login": "عرض نافذة \"ما الجديد\" عند تسجيل الدخول",
"Show Admin Details in Account Pending Overlay": "عرض تفاصيل المشرف في نافذة \"الحساب قيد الانتظار\"",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1669,6 +1672,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "Разреши контроли на чата",
"Allow Chat Delete": "Разреши изтриване на чат",
"Allow Chat Deletion": "Позволи Изтриване на Чат",
"Allow Chat Edit": "Разреши редактиране на чат",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "Довършвания",
"Compress Images in Channels": "",
"Concurrent Requests": "Едновременни заявки",
"Config": "",
"Config imported successfully": "",
"Configure": "Конфигуриране",
"Confirm": "Потвърди",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Експортване на всички чатове (за всички потребители)",
"Export chat (.json)": "Експортиране на чат (.json)",
"Export Chats": "Експортване на чатове",
"Export Config to JSON File": "Експортиране на конфигурацията в JSON файл",
"Export Config": "",
"Export Models": "",
"Export Presets": "Експортиране на предварителни настройки",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Импортване на чатове",
"Import Config from JSON File": "Импортиране на конфигурация от JSON файл",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "Импортиране на предварителни настройки",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Организирайте вашите потребители",
"Other": "Друго",
"Output": "",
"OUTPUT": "ИЗХОД",
"Output format": "Изходен формат",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Изтриване на модела",
"Rename": "Преименуване",
"Render Markdown in Previews": "",
"Reorder Models": "Преорганизиране на моделите",
"Reply": "",
"Reply in Thread": "Отговори в тред",
@@ -1638,6 +1640,7 @@
"Show": "Покажи",
"Show \"What's New\" modal on login": "Покажи модалния прозорец \"Какво е ново\" при вписване",
"Show Admin Details in Account Pending Overlay": "Покажи детайлите на администратора в наслагването на изчакващ акаунт",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "চ্যাট ডিলিট করতে দিন",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "সমকালীন অনুরোধ",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "সব চ্যাট এক্সপোর্ট করুন (সব ইউজারের)",
"Export chat (.json)": "",
"Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "চ্যাটগুলি ইমপোর্ট করুন",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "অন্যান্য",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "মডেল রিমুভ করুন",
"Rename": "রেনেম",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "দেখান",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "ཁ་བརྡའི་ཚོད་འཛིན་ལ་གནང་བ་སྤྲོད་པ།",
"Allow Chat Delete": "ཁ་བརྡ་བསུབ་པར་གནང་བ་སྤྲོད་པ།",
"Allow Chat Deletion": "ཁ་བརྡ་བསུབ་པར་གནང་བ་སྤྲོད་པ།",
"Allow Chat Edit": "ཁ་བརྡ་ཞུ་དག་ལ་གནང་བ་སྤྲོད་པ།",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "འགྲུབ་པ།",
"Compress Images in Channels": "",
"Concurrent Requests": "མཉམ་ལས་རེ་ཞུ།",
"Config": "",
"Config imported successfully": "",
"Configure": "སྒྲིག་འགོད།",
"Confirm": "གཏན་འཁེལ།",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "ཁ་བརྡ་ཡོངས་རྫོགས་ཕྱིར་གཏོང་། (བེད་སྤྱོད་མཁན་ཡོངས་)",
"Export chat (.json)": "ཁ་བརྡ་ཕྱིར་གཏོང་ (.json)",
"Export Chats": "ཁ་བརྡ་ཕྱིར་གཏོང་།",
"Export Config to JSON File": "སྒྲིག་འགོད་ JSON ཡིག་ཆར་ཕྱིར་གཏོང་།",
"Export Config": "",
"Export Models": "",
"Export Presets": "སྔོན་སྒྲིག་ཕྱིར་གཏོང་།",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "ཁ་བརྡ་ནང་འདྲེན།",
"Import Config from JSON File": "JSON ཡིག་ཆ་ནས་སྒྲིག་འགོད་ནང་འདྲེན།",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "སྔོན་སྒྲིག་ནང་འདྲེན།",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "ཁྱེད་ཀྱི་བེད་སྤྱོད་མཁན་སྒྲིག་འཛུགས།",
"Other": "གཞན།",
"Output": "",
"OUTPUT": "ཐོན་འབྲས།",
"Output format": "ཐོན་འབྲས་ཀྱི་བཀོད་པ།",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "དཔེ་དབྱིབས་འདོར་བ།",
"Rename": "མིང་བསྐྱར་འདོགས།",
"Render Markdown in Previews": "",
"Reorder Models": "དཔེ་དབྱིབས་བསྐྱར་སྒྲིག",
"Reply": "",
"Reply in Thread": "བརྗོད་གཞིའི་ནང་ལན་འདེབས།",
@@ -1637,6 +1639,7 @@
"Show": "སྟོན་པ།",
"Show \"What's New\" modal on login": "ནང་འཛུལ་སྐབས་ \"གསར་པ་ཅི་ཡོད\" modal སྟོན་པ།",
"Show Admin Details in Account Pending Overlay": "རྩིས་ཁྲ་སྒུག་བཞིན་པའི་གཏོགས་ངོས་སུ་དོ་དམ་པའི་ཞིབ་ཕྲ་སྟོན་པ།",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1664,6 +1667,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Dozvoli poziv",
"Allow Chat Controls": "Dozvoli kontrolu razgovora",
"Allow Chat Delete": "Dozvoli brisanaje razgovora",
"Allow Chat Deletion": "Dopusti brisanje razgovora",
"Allow Chat Edit": "Dozvoli uredjivanje razgovora",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "Istodobni zahtjevi",
"Config": "",
"Config imported successfully": "",
"Configure": "Promijeni",
"Confirm": "Potvrdi",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Izvoz svih razgovora (svi korisnici)",
"Export chat (.json)": "Izvoz četa (.json)",
"Export Chats": "Izvoz razgovora",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Uvoz razgovora",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "Ostalo",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Ukloni model",
"Rename": "Preimenuj",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1639,6 +1641,7 @@
"Show": "Pokaži",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Permetre la trucada",
"Allow Chat Controls": "Permetre els controls de xat",
"Allow Chat Delete": "Permetre eliminar el xat",
"Allow Chat Deletion": "Permetre la supressió del xat",
"Allow Chat Edit": "Permetre editar el xat",
"Allow Chat Export": "Permetre exportar el xat",
"Allow Chat Params": "Permetre els paràmetres de xat",
@@ -346,6 +345,7 @@
"Completions": "Completaments",
"Compress Images in Channels": "Comprimir imatges en els canals",
"Concurrent Requests": "Peticions simultànies",
"Config": "",
"Config imported successfully": "Configuració importada correctament",
"Configure": "Configurar",
"Confirm": "Confirmar",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exportar tots els xats (Tots els usuaris)",
"Export chat (.json)": "Exportar el xat (.json)",
"Export Chats": "Exportar els xats",
"Export Config to JSON File": "Exportar la configuració a un arxiu JSON",
"Export Config": "",
"Export Models": "Exportar els models",
"Export Presets": "Exportar les configuracions",
"Export Prompts": "Exportar les indicacions",
@@ -976,7 +976,7 @@
"Images": "Imatges",
"Import": "Importar",
"Import Chats": "Importar xats",
"Import Config from JSON File": "Importar la configuració des d'un arxiu JSON",
"Import Config": "",
"Import From Link": "Importar des d'un enllaç",
"Import Models": "Importar models",
"Import Presets": "Importar configuracions",
@@ -1342,6 +1342,7 @@
"Ordered List": "Llista ordenada",
"Organize your users": "Organitza els teus usuaris",
"Other": "Altres",
"Output": "",
"OUTPUT": "SORTIDA",
"Output format": "Format de sortida",
"Output Format": "Format de sortida",
@@ -1478,6 +1479,7 @@
"Remove image": "Eliminar imatge",
"Remove Model": "Eliminar el model",
"Rename": "Canviar el nom",
"Render Markdown in Previews": "",
"Reorder Models": "Reordenar els models",
"Reply": "Respondre",
"Reply in Thread": "Respondre al fil",
@@ -1639,6 +1641,7 @@
"Show": "Mostrar",
"Show \"What's New\" modal on login": "Veure 'Què hi ha de nou' a l'entrada",
"Show Admin Details in Account Pending Overlay": "Mostrar els detalls de l'administrador a la superposició del compte pendent",
"Show all ({{COUNT}} characters)": "",
"Show Files": "Mostra els arxius",
"Show Formatting Toolbar": "Mostrar la barra de format",
"Show image preview": "Mostrar la previsualització de la imatge",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "Habilitat actualitzada correctament",
"Skills": "Habilitats",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Ometre la memòria cau",
"Skip the cache and re-run the inference. Defaults to False.": "Omet la memòria cai i torna a executar la inferència. Per defecte és Fals.",
"Something went wrong :/": "Quelcom no ha anat bé",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "Tugoti nga mapapas ang mga chat",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "I-export ang tanan nga mga chat (Tanan nga tiggamit)",
"Export chat (.json)": "",
"Export Chats": "I-export ang mga chat",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Import nga mga chat",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "",
"Rename": "",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "Pagpakita",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Povolit volání",
"Allow Chat Controls": "Povolit ovládací prvky chatu",
"Allow Chat Delete": "Povolit smazání konverzace",
"Allow Chat Deletion": "Povolit smazání konverzace",
"Allow Chat Edit": "Povolit úpravu konverzace",
"Allow Chat Export": "Povolit export konverzace",
"Allow Chat Params": "Povolit parametry chatu",
@@ -346,6 +345,7 @@
"Completions": "Dokončení",
"Compress Images in Channels": "Komprimovat obrázky v kanálech",
"Concurrent Requests": "Souběžné požadavky",
"Config": "",
"Config imported successfully": "Konfigurace byla úspěšně importována",
"Configure": "Konfigurovat",
"Confirm": "Potvrdit",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exportovat všechny konverzace (všichni uživatelé)",
"Export chat (.json)": "Exportovat konverzaci (.json)",
"Export Chats": "Exportovat konverzace",
"Export Config to JSON File": "Exportovat konfiguraci do souboru JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "Exportovat předvolby",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Importovat",
"Import Chats": "Importovat konverzace",
"Import Config from JSON File": "Importovat konfiguraci ze souboru JSON",
"Import Config": "",
"Import From Link": "Importovat z odkazu",
"Import Models": "",
"Import Presets": "Importovat předvolby",
@@ -1342,6 +1342,7 @@
"Ordered List": "Číslovaný seznam",
"Organize your users": "Uspořádejte své uživatele",
"Other": "Jiné",
"Output": "",
"OUTPUT": "VÝSTUP",
"Output format": "Formát výstupu",
"Output Format": "Formát výstupu",
@@ -1478,6 +1479,7 @@
"Remove image": "Odebrat obrázek",
"Remove Model": "Odebrat model",
"Rename": "Přejmenovat",
"Render Markdown in Previews": "",
"Reorder Models": "Změnit pořadí modelů",
"Reply": "",
"Reply in Thread": "Odpovědět ve vlákně",
@@ -1640,6 +1642,7 @@
"Show": "Zobrazit",
"Show \"What's New\" modal on login": "Zobrazit okno \"Co je nového\" při přihlášení",
"Show Admin Details in Account Pending Overlay": "Zobrazit podrobnosti administrátora v překryvné vrstvě čekajícího účtu",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "Zobrazit panel nástrojů pro formátování",
"Show image preview": "Zobrazit náhled obrázku",
@@ -1667,6 +1670,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Přeskočit mezipaměť",
"Skip the cache and re-run the inference. Defaults to False.": "Přeskočit mezipaměť a znovu spustit inferenci. Výchozí hodnota je False.",
"Something went wrong :/": "Něco se pokazilo :/",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Tillad kald",
"Allow Chat Controls": "Tillad kontrol af chats",
"Allow Chat Delete": "Tillad sletning af chats",
"Allow Chat Deletion": "Tillad sletning af chats",
"Allow Chat Edit": "Tillad redigering af chats",
"Allow Chat Export": "Tillad eksport af chats",
"Allow Chat Params": "Tillad parametre i chats",
@@ -346,6 +345,7 @@
"Completions": "Completions",
"Compress Images in Channels": "Komprimér billeder i kanaler",
"Concurrent Requests": "Concurrent requests",
"Config": "",
"Config imported successfully": "Konfiguration importeret",
"Configure": "Konfigurer",
"Confirm": "Bekræft",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Eksportér alle chats (alle brugere)",
"Export chat (.json)": "Eksportér chat (.json)",
"Export Chats": "Eksportér chats",
"Export Config to JSON File": "Eksportér konfiguration til JSON-fil",
"Export Config": "",
"Export Models": "Eksportér modeller",
"Export Presets": "Eksportér indstillinger",
"Export Prompts": "Eksportér prompter",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Importer",
"Import Chats": "Importer chats",
"Import Config from JSON File": "Importer konfiguration fra JSON-fil",
"Import Config": "",
"Import From Link": "Importer fra et link",
"Import Models": "Importer modeller",
"Import Presets": "Importer Presets",
@@ -1342,6 +1342,7 @@
"Ordered List": "Nummereret liste",
"Organize your users": "Organiser dine brugere",
"Other": "Andet",
"Output": "",
"OUTPUT": "OUTPUT",
"Output format": "Outputformat",
"Output Format": "Output format",
@@ -1478,6 +1479,7 @@
"Remove image": "Fjern billede",
"Remove Model": "Fjern model",
"Rename": "Omdøb",
"Render Markdown in Previews": "",
"Reorder Models": "Omarranger modeller",
"Reply": "Svar",
"Reply in Thread": "Svar i tråd",
@@ -1638,6 +1640,7 @@
"Show": "Vis",
"Show \"What's New\" modal on login": "Vis \"Hvad er nyt\" modal ved login",
"Show Admin Details in Account Pending Overlay": "Vis administratordetaljer i overlay for ventende konto",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "Vis formateringsværktøjslinjen",
"Show image preview": "Vis billedforhåndsvisning",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Spring cache over",
"Skip the cache and re-run the inference. Defaults to False.": "Spring cachen over og kør inferensen igen. Standard er falsk.",
"Something went wrong :/": "Noget gik galt :/",
+13 -8
View File
@@ -98,7 +98,6 @@
"Allow Call": "Anruffunktion erlauben",
"Allow Chat Controls": "Chat-Steuerung erlauben",
"Allow Chat Delete": "Löschen von Chats erlauben",
"Allow Chat Deletion": "Löschen von Chats erlauben",
"Allow Chat Edit": "Bearbeiten von Chats erlauben",
"Allow Chat Export": "Chat-Export erlauben",
"Allow Chat Params": "Chat-Parameter erlauben",
@@ -273,7 +272,7 @@
"Chat Permissions": "Chat-Berechtigungen",
"Chat Tags Auto-Generation": "Automatische Chat-Tag-Generierung",
"Chat unshared successfully.": "Chat-Freigabe erfolgreich aufgehoben",
"chats": "",
"chats": "Chats",
"Chats": "Chats",
"Check Again": "Erneut prüfen",
"Check for updates": "Nach Updates suchen",
@@ -346,6 +345,7 @@
"Completions": "Vervollständigungen",
"Compress Images in Channels": "Bilder in Kanälen komprimieren",
"Concurrent Requests": "Gleichzeitige Anfragen",
"Config": "",
"Config imported successfully": "Konfiguration erfolgreich importiert",
"Configure": "Konfigurieren",
"Confirm": "Bestätigen",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Alle Chats exportieren (Alle Benutzer)",
"Export chat (.json)": "Chat exportieren (.json)",
"Export Chats": "Chats exportieren",
"Export Config to JSON File": "Konfiguration als JSON exportieren",
"Export Config": "",
"Export Models": "Modelle exportieren",
"Export Presets": "Vorlagen exportieren",
"Export Prompts": "Prompts exportieren",
@@ -976,7 +976,7 @@
"Images": "Bilder",
"Import": "Importieren",
"Import Chats": "Chats importieren",
"Import Config from JSON File": "Konfiguration aus JSON importieren",
"Import Config": "",
"Import From Link": "Von Link importieren",
"Import Models": "Modelle importieren",
"Import Presets": "Vorlagen importieren",
@@ -1342,6 +1342,7 @@
"Ordered List": "Nummerierte Liste",
"Organize your users": "Organisieren Sie Ihre Benutzer",
"Other": "Andere",
"Output": "",
"OUTPUT": "AUSGABE",
"Output format": "Ausgabeformat",
"Output Format": "Ausgabeformat",
@@ -1478,6 +1479,7 @@
"Remove image": "Bild entfernen",
"Remove Model": "Modell entfernen",
"Rename": "Umbenennen",
"Render Markdown in Previews": "",
"Reorder Models": "Modelle neu anordnen",
"Reply": "Antworten",
"Reply in Thread": "Im Thread antworten",
@@ -1638,6 +1640,7 @@
"Show": "Anzeigen",
"Show \"What's New\" modal on login": "\"Was gibt's Neues\"-Fenster beim Anmelden anzeigen",
"Show Admin Details in Account Pending Overlay": "Admin-Details im 'Konto ausstehend'-Overlay anzeigen",
"Show all ({{COUNT}} characters)": "",
"Show Files": "Dateien anzeigen",
"Show Formatting Toolbar": "Formatierungsleiste anzeigen",
"Show image preview": "Bildvorschau anzeigen",
@@ -1654,7 +1657,7 @@
"Sign up to {{WEBUI_NAME}}": "Bei {{WEBUI_NAME}} registrieren",
"Significantly improves accuracy by using an LLM to enhance tables, forms, inline math, and layout detection. Will increase latency. Defaults to False.": "Verbessert die Genauigkeit erheblich, indem ein LLM zur Optimierung von Tabellen, Formularen, mathematischen Formeln und Layout-Erkennung genutzt wird. Erhöht die Latenz. Standardmäßig deaktiviert.",
"Signing in to {{WEBUI_NAME}}": "Anmeldung bei {{WEBUI_NAME}}...",
"Single": "",
"Single": "Einzeln",
"Sink List": "Sink-Liste",
"sk-1234": "sk-1234",
"Skill created successfully": "Skill erfolgreich erstellt",
@@ -1664,7 +1667,9 @@
"Skill Name": "Skill Name",
"Skill updated successfully": "Skill erfolgreich aktualisiert",
"Skills": "Skills",
"Skills Access": "",
"Skills Access": "Skills Zugang",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Cache überspringen",
"Skip the cache and re-run the inference. Defaults to False.": "Cache überspringen und die Inferenz erneut ausführen. Standardmäßig deaktiviert.",
"Something went wrong :/": "Etwas ist schiefgelaufen :/",
@@ -1818,7 +1823,7 @@
"Toggle whether current connection is active.": "Umschalten, ob die aktuelle Verbindung aktiv ist.",
"Token": "Token",
"Token counts are estimates and may not reflect actual API usage": "Token-Anzahlen sind Schätzwerte und entsprechen möglicherweise nicht der tatsächlichen API-Nutzung.",
"tokens": "",
"tokens": "Token",
"Tokens": "Token",
"Too verbose": "Zu ausführlich",
"Tool created successfully": "Werkzeug erfolgreich erstellt",
@@ -1892,7 +1897,7 @@
"URL is required": "URL ist erforderlich",
"URL Mode": "URL-Modus",
"Usage": "Nutzung",
"Use": "",
"Use": "Nutze",
"Use '#' in the prompt input to load and include your knowledge.": "Verwenden Sie '#' in der Prompt-Eingabe, um Ihr Wissen zu laden und einzubeziehen.",
"Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "Verwenden Sie den Endpunkt /v1/chat/completions statt /v1/audio/transcriptions für potenziell bessere Genauigkeit.",
"Use Chat Completions API": "Chat Completions API verwenden",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "Allow Delete Chats",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Export All Chats (All Doggos)",
"Export chat (.json)": "",
"Export Chats": "Export Barks",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Import Barks",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "",
"Rename": "",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "Show much show",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Επιτρέπεται η Κλήση",
"Allow Chat Controls": "Επιτρέπεται ο Έλεγχος Συνομιλίας",
"Allow Chat Delete": "Επιτρέπεται η διαγραφή συνομιλίας",
"Allow Chat Deletion": "Επιτρέπεται η Διαγραφή Συνομιλίας",
"Allow Chat Edit": "Επιτρέπεται η Επεξεργασία Συνομιλίας",
"Allow Chat Export": "Επιτρέπεται η Εξαγωγή Συνομιλίας",
"Allow Chat Params": "Επιτρέπονται οι Παράμετροι Συνομιλίας",
@@ -346,6 +345,7 @@
"Completions": "Ολοκληρώσεις",
"Compress Images in Channels": "Συμπίεση εικόνων σε κανάλια",
"Concurrent Requests": "Ταυτόχρονες Αιτήσεις",
"Config": "",
"Config imported successfully": "",
"Configure": "Διαμόρφωση",
"Confirm": "Επιβεβαίωση",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Εξαγωγή Όλων των Συνομιλιών (Όλοι οι Χρήστες)",
"Export chat (.json)": "Εξαγωγή συνομιλίας (.json)",
"Export Chats": "Εξαγωγή Συνομιλιών",
"Export Config to JSON File": "Εξαγωγή Διαμόρφωσης σε Αρχείο JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "Εξαγωγή Προκαθορισμένων",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Εισαγωγή",
"Import Chats": "Εισαγωγή Συνομιλιών",
"Import Config from JSON File": "Εισαγωγή Διαμόρφωσης από Αρχείο JSON",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "Εισαγωγή Προκαθορισμένων",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Οργανώστε τους χρήστες σας",
"Other": "Άλλο",
"Output": "",
"OUTPUT": "ΕΞΟΔΟΣ",
"Output format": "Μορφή εξόδου",
"Output Format": "Μορφή Εξόδου",
@@ -1478,6 +1479,7 @@
"Remove image": "Αφαίρεση εικόνας",
"Remove Model": "Αφαίρεση Μοντέλου",
"Rename": "Μετονομασία",
"Render Markdown in Previews": "",
"Reorder Models": "Επαναταξινόμηση Μοντέλων",
"Reply": "Απάντηση",
"Reply in Thread": "Απάντηση στο Νήμα Συζήτησης",
@@ -1638,6 +1640,7 @@
"Show": "Εμφάνιση",
"Show \"What's New\" modal on login": "Εμφάνιση του παράθυρου \"Τι νέο υπάρχει\" κατά την είσοδο",
"Show Admin Details in Account Pending Overlay": "Εμφάνιση Λεπτομερειών Διαχειριστή στο Υπέρθεση Εκκρεμής Λογαριασμού",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "",
"Export chat (.json)": "",
"Export Chats": "",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Organise your users",
"Other": "",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "",
"Rename": "",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "",
"Export chat (.json)": "",
"Export Chats": "",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "",
"Rename": "",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+138 -133
View File
@@ -35,7 +35,7 @@
"Accept Autocomplete Generation\nJump to Prompt Variable": "Aceptar Generar Autocompletado\nIr a la Configuración de este Indicador",
"Access": "Acceso",
"Access Control": "Control de Acceso",
"Access List": "",
"Access List": "Lista de Acceso",
"Accessible to all users": "Accesible para todos los usuarios",
"Account": "Cuenta",
"Account Activation Pending": "Activación de cuenta Pendiente",
@@ -53,8 +53,8 @@
"Add a model ID": "Añadir un ID de modelo",
"Add a short description about what this model does": "Añadir una breve descripción sobre lo que hace este modelo",
"Add a tag": "Añadir una etiqueta",
"Add a tag...": "",
"Add Access": "",
"Add a tag...": "Añadir una etiqueta...",
"Add Access": "Añador Acceso",
"Add Arena Model": "Añadir modelo a la Arena",
"Add Connection": "Añadir Conexión",
"Add Content": "Añadir Contenido",
@@ -63,13 +63,13 @@
"Add Custom Prompt": "Añadir Indicador Personalizado",
"Add Details": "Añadir Detalles",
"Add Files": "Añadir Archivos",
"Add Image": "",
"Add Image": "Añadir Imagen",
"Add Member": "Añadir Miembro",
"Add Members": "Añadir Miembros",
"Add Memory": "Añadir Memoria",
"Add Model": "Añadir Modelo",
"Add Reaction": "Añadir Reacción",
"Add tag": "",
"Add tag": "Añadir Etiqueta",
"Add Tag": "Añadir etiqueta",
"Add text content": "Añade contenido de texto",
"Add User": "Añadir Usuario",
@@ -94,11 +94,10 @@
"All": "Todos",
"All chats have been unarchived.": "Todos los chats han sido desarchivados",
"All models deleted successfully": "Todos los modelos borrados correctamente",
"All Users": "",
"All Users": "Todos los Usuarios",
"Allow Call": "Permitir Llamada",
"Allow Chat Controls": "Permitir Controles del Chat",
"Allow Chat Delete": "Permitir Borrar Chat",
"Allow Chat Deletion": "Permitir Borrado de Chat",
"Allow Chat Edit": "Permitir Editar Chat",
"Allow Chat Export": "Permitir Exportar Chat",
"Allow Chat Params": "Permitir Parametros en Chat",
@@ -145,7 +144,7 @@
"API Keys": "Claves AI",
"API Mode": "Modo de API",
"API Timeout": "API Timeout",
"API Type": "",
"API Type": "Tipo de API",
"API Version": "Versión API",
"API Version is required": "La Versión API es requerida",
"Application DN": "Aplicacion DN",
@@ -153,18 +152,18 @@
"applies to all users with the \"user\" role": "se aplica a todos los usuarios con el rol \"user\" ",
"April": "Abril",
"Archive": "Archivar",
"Archive All": "",
"Archive All": "Arcbhivar Todos",
"Archive All Chats": "Archivar Todos los Chats",
"Archived Chats": "Chats archivados",
"archived-chat-export": "exportar chats archivados",
"Are you sure you want to archive all chats? This action cannot be undone.": "",
"Are you sure you want to clear all memories? This action cannot be undone.": "¿Segur@ de que quieres borrar todas las memorias? (¡esta acción NO se puede deshacer!)",
"Are you sure you want to delete \"{{NAME}}\"?": "¿Segur@ de que quieres eliminar \"{{NAME}}\"?",
"Are you sure you want to delete all chats? This action cannot be undone.": "",
"Are you sure you want to delete this channel?": "¿Segur@ de que quieres eliminar este canal?",
"Are you sure you want to delete this message?": "¿Segur@ de que quieres eliminar este mensaje? ",
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "",
"Are you sure you want to unarchive all archived chats?": "¿Segur@ de que quieres desarchivar todos los chats archivados?",
"Are you sure you want to archive all chats? This action cannot be undone.": "¿Seguro que quieres archivar todos los chats? Esta acción no se puede deshacer.",
"Are you sure you want to clear all memories? This action cannot be undone.": "¿Seguro de que quieres borrar todas las memorias? (¡esta acción NO se puede deshacer!)",
"Are you sure you want to delete \"{{NAME}}\"?": "¿Seguro de que quieres eliminar \"{{NAME}}\"?",
"Are you sure you want to delete all chats? This action cannot be undone.": "¿Seguro que quieres borrar todos los chats? Esta acción no se puede deshacer.",
"Are you sure you want to delete this channel?": "¿Seguro de que quieres eliminar este canal?",
"Are you sure you want to delete this message?": "¿Seguro de que quieres eliminar este mensaje? ",
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "¿Seguro que desea eliminar esta versión? Las versiones secundarias se vincularán a la versión principal de esta.",
"Are you sure you want to unarchive all archived chats?": "¿Seguro de que quieres desarchivar todos los chats archivados?",
"Arena Models": "Arena de Modelos",
"Artifacts": "Artefactos",
"Asc": "Asc",
@@ -224,7 +223,7 @@
"Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "Impulsando o penalizando tokens específicos para respuestas restringidas. Los valores de sesgo se limitarán entre -100 y 100 (inclusive). (Por defecto: ninguno)",
"Brave": "Brave",
"Brave Search API Key": "Clave API de Brave Search",
"Browse and query knowledge bases": "",
"Browse and query knowledge bases": "Explorar y consultar bases de conocimiento",
"Builtin Tools": "Herramientas Integradas",
"Bullet List": "Lista de Viñetas",
"Button ID": "ID del Botón",
@@ -241,7 +240,7 @@
"Camera": "Cámara",
"Cancel": "Cancelar",
"Cannot create an empty note.": "No se puede crear una nota vacía.",
"Cannot delete the production version": "",
"Cannot delete the production version": "No se puede borrar la versión de producción",
"Capabilities": "Capacidades",
"Capture": "Captura",
"Capture Audio": "Capturar Audio",
@@ -261,19 +260,19 @@
"Chat": "Chat",
"Chat Background Image": "Imágen de Fondo del Chat",
"Chat Bubble UI": "Interfaz del Chat en Burbuja",
"Chat Completions": "",
"Chat Completions": "Completación del Chat",
"Chat Controls": "Controles del Chat",
"Chat Conversation": "Conversación del Chat",
"Chat direction": "Dirección del Chat",
"Chat exported successfully": "",
"Chat History": "",
"Chat exported successfully": "Chat exportado correctmente",
"Chat History": "Historial del Chat",
"Chat ID": "ID del Chat",
"Chat moved successfully": "Chat movido correctamente",
"Chat Overview": "Vista General del Chat",
"Chat Permissions": "Permisos del Chat",
"Chat Tags Auto-Generation": "AutoGeneración de Etiquetas de Chat",
"Chat unshared successfully.": "",
"chats": "",
"Chat unshared successfully.": "Chat descompartido correctamente",
"chats": "chats",
"Chats": "Chats",
"Check Again": "Verifica de nuevo",
"Check for updates": "Buscar actualizaciones",
@@ -302,7 +301,7 @@
"Click here to upload a workflow.json file.": "Pulsa aquí para subir un archivo workflow.json",
"click here.": "Pulsa aquí.",
"Click on the user role button to change a user's role.": "Pulsa en el botón rol de usuario para cambiar su rol.",
"Click to copy ID": "",
"Click to copy ID": "Pulsa para copiar ID",
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permisos de escritura del portapapeles denegado. Por favor, comprueba la configuración de tu navegador para otorgar el permiso necesario.",
"Clone": "Clonar",
"Clone Chat": "Clonar Chat",
@@ -338,14 +337,15 @@
"ComfyUI Workflow": "Flujo de Trabajo de ComfyUI",
"ComfyUI Workflow Nodes": "Nodos del Flujo de Trabajo de ComfyUI",
"Comma separated Node Ids (e.g. 1 or 1,2)": "IDs de Nodo separados por comas (ej. 1 o 1,2)",
"command": "",
"command": "comando",
"Command": "Comando",
"Comment": "Comentario",
"Commit Message": "",
"Community Reviews": "",
"Commit Message": "Corregir Mensaje",
"Community Reviews": "Revisiones de la Comunidad",
"Completions": "Cumplimientos",
"Compress Images in Channels": "Comprimir Imágenes en Canales",
"Concurrent Requests": "Número de Solicitudes Concurrentes",
"Config": "",
"Config imported successfully": "Configuración importada correctamente",
"Configure": "Configurar",
"Confirm": "Confirmar",
@@ -385,8 +385,8 @@
"Copy Last Response": "Copiar la última Respuesta",
"Copy link": "Copiar Enlace",
"Copy Link": "Copiar enlace",
"Copy Prompt": "",
"Copy Share Link": "",
"Copy Prompt": "Copiar Indicador",
"Copy Share Link": "Copiar Enlace Compartido",
"Copy to clipboard": "Copia a portapapeles",
"Copy URL": "Copiar URL",
"Copying to clipboard was successful!": "¡La copia al portapapeles se ha realizado correctamente!",
@@ -453,13 +453,13 @@
"Default User Role": "Rol predeterminado de los nuevos usuarios",
"Delete": "Borrar",
"Delete a model": "Borrar un modelo",
"Delete All": "",
"Delete All": "Borrar Todo",
"Delete All Chats": "Borrar todos los chats",
"Delete all contents inside this folder": "Borrar todo el contenido de esta carpeta",
"Delete All Models": "Borrar todos los modelos",
"Delete Chat": "Borrar Chat",
"Delete chat?": "¿Borrar el chat?",
"Delete File": "",
"Delete File": "Borrar Fichero",
"Delete folder?": "¿Borrar carpeta?",
"Delete function?": "Borrar la función?",
"Delete Message": "Borrar mensaje",
@@ -467,20 +467,20 @@
"Delete Model": "Borrar Modelo",
"Delete note?": "¿Borrar nota?",
"Delete prompt?": "¿Borrar el indicador?",
"Delete skill?": "",
"Delete skill?": "¿Borrar habilidades?",
"delete this link": "Borrar este enlace",
"Delete tool?": "¿Borrar la herramienta?",
"Delete User": "Borrar Usuario",
"Delete Version": "",
"Delete Version": "Borrar Versión",
"Deleted": "Borrado",
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} Borrado",
"Deleted {{name}}": "{{nombre}} Borrado",
"Deleted User": "Usuario Borrado",
"Deployment names are required for Azure OpenAI": "Azure OpenAI requiere nombres de implementación",
"Desc": "Desc",
"Describe the edit...": "",
"Describe the image...": "",
"Describe what changed...": "",
"Describe the edit...": "Describe la edición...",
"Describe the image...": "Describe la Imagen...",
"Describe what changed...": "Describe lo cambiado...",
"Describe your knowledge base and objectives": "Describe tu Base de Conocimientos y sus objetivos",
"Description": "Descripción",
"Detect Artifacts Automatically": "Detectar Artefactos Automáticamente",
@@ -492,7 +492,7 @@
"Direct Message": "Mensaje Directo",
"Direct Tool Servers": "Servidores de Herramientas Directos",
"Directory selection was cancelled": "La selección de directorio ha sido cancelada",
"Disable All": "",
"Disable All": "Deshabilitar Todo",
"Disable Code Interpreter": "Deshabilitar Interprete de Código",
"Disable Image Extraction": "Deshabilitar Extracción de Imágenes",
"Disable image extraction from the PDF. If Use LLM is enabled, images will be automatically captioned. Defaults to False.": "Desabilita la extracción de imágenes del pdf. Si está habilitado Usar LLM las imágenes se capturan automáticamente. Por defecto el valor es Falso (las imágenes se extraen).",
@@ -552,15 +552,15 @@
"e.g. A filter to remove profanity from text": "p.ej. Un filtro para eliminar malas palabras del texto",
"e.g. about the Roman Empire": "p.ej. sobre el imperio romano",
"e.g. alloy, echo, shimmer": "p.ej. aleación, eco, brillo",
"e.g. Code Review Guidelines": "",
"e.g. code-review-guidelines": "",
"e.g. Code Review Guidelines": "p.ej. Directrices de Revisión de Código",
"e.g. code-review-guidelines": "p.ej. directrices-revisión-código",
"e.g. en": "p.ej. es",
"e.g. My Filter": "p.ej. Mi Filtro",
"e.g. My Tools": "p.ej. Mis Herramientas",
"e.g. my_filter": "p.ej. mi_filtro",
"e.g. my_tools": "p.ej. mis_herramientas",
"e.g. pdf, docx, txt": "p.ej. pdf, docx, txt ...",
"e.g. Step-by-step instructions for code reviews": "",
"e.g. Step-by-step instructions for code reviews": "p.ej. Unstruciones paso a paso para revisión de código",
"e.g. Tell me a fun fact": "p.ej. Dime algo divertido",
"e.g. Tell me a fun fact about the Roman Empire": "p.ej. Dime algo divertido sobre el imperio romano",
"e.g. Tools for performing various operations": "p.ej. Herramientas para realizar diversas operaciones",
@@ -577,7 +577,7 @@
"Edit Image": "Editar Imagen",
"Edit Last Message": "Editar Último Mensaje",
"Edit Memory": "Editar Memoria",
"Edit Prompt": "",
"Edit Prompt": "Editar Indicador",
"Edit User": "Editar Usuario",
"Edit User Group": "Editar Grupo de Usuarios",
"Edit workflow.json content": "Editar el contenido de workflow.json",
@@ -592,15 +592,15 @@
"Embedding Batch Size": "Tamaño del Lote de Incrustación",
"Embedding Model": "Modelo de Incrustación",
"Embedding Model Engine": "Motor del Modelo de Incrustación",
"Enable All": "",
"Enable API Keys": "Activar Claves API",
"Enable All": "Habilitar Todo",
"Enable API Keys": "Habilitar Claves API",
"Enable autocomplete generation for chat messages": "Habilitar generación de autocompletado para mensajes de chat",
"Enable Code Execution": "Habilitar Ejecución de Código",
"Enable Code Interpreter": "Habilitar Interprete de Código",
"Enable Community Sharing": "Habilitar Compartir con la Comunidad",
"Enable Memory Locking (mlock) to prevent model data from being swapped out of RAM. This option locks the model's working set of pages into RAM, ensuring that they will not be swapped out to disk. This can help maintain performance by avoiding page faults and ensuring fast data access.": "Habilitar bloqueo de memoria (mlock) para prevenir que los datos del modelo se intercambien fuera de la RAM. Esta opción bloquea el conjunto de páginas de trabajo del modelo en RAM, asegurando que no se intercambiarán fuera a disco. Esto puede ayudar a mantener el rendimiento evitando fallos de página y asegurando un acceso rápido a los datos.",
"Enable Memory Mapping (mmap) to load model data. This option allows the system to use disk storage as an extension of RAM by treating disk files as if they were in RAM. This can improve model performance by allowing for faster data access. However, it may not work correctly with all systems and can consume a significant amount of disk space.": "Habilitar Mapeado de Memoria (mmap) para cargar datos del modelo. Esta opción permite al sistema usar el almacenamiento del disco como una extensión de la RAM al tratar los archivos en disco como si estuvieran en la RAM. Esto puede mejorar el rendimiento del modelo al permitir un acceso más rápido a los datos. Sin embargo, puede no funcionar correctamente con todos los sistemas y puede consumir una cantidad significativa de espacio en disco.",
"Enable Message Queue": "",
"Enable Message Queue": "Habilitar Cola de Mensajes",
"Enable Message Rating": "Habilitar Calificación de los Mensajes",
"Enable Mirostat sampling for controlling perplexity.": "Algoritmo de decodificación de texto neuronal que controla activamente el proceso generativo para mantener la perplejidad del texto generado en un valor deseado. Previene las trampas de aburrimiento (por excesivas repeticiones) y de incoherencia (por generación de excesivo texto).",
"Enable New Sign Ups": "Habilitar Registros de Nuevos Usuarios",
@@ -699,7 +699,7 @@
"Enter server host": "Ingresar host del servidor",
"Enter server label": "Ingresar etiqueta del servidor",
"Enter server port": "Ingresar puerto del servidor",
"Enter skill instructions in markdown...": "",
"Enter skill instructions in markdown...": "Ingresar instrucciones de la habilidad en markdown...",
"Enter Sougou Search API sID": "Ingresar Sougou Search API sID",
"Enter Sougou Search API SK": "Ingresar Sougou Search API SK",
"Enter stop sequence": "Ingresar secuencia de parada",
@@ -722,8 +722,8 @@
"Enter Yacy Password": "Ingresar Contraseña de Yacy",
"Enter Yacy URL (e.g. http://yacy.example.com:8090)": "Ingresar URL de Yacy (p.ej. http://yacy.ejemplo.com:8090)",
"Enter Yacy Username": "Ingresar Nombre de Usuario de Yacy",
"Enter Yandex Web Search API Key": "",
"Enter Yandex Web Search URL": "",
"Enter Yandex Web Search API Key": "Ingresar API de la Busqueda Web de Yandex",
"Enter Yandex Web Search URL": "Ingresar URL de la Busqueda Wev de Yandex",
"Enter your code here...": "Introduce tu código aquí...",
"Enter your current password": "Ingresa tu contraseña actual",
"Enter Your Email": "Ingresa tu correo electrónico",
@@ -757,7 +757,7 @@
"Example: sAMAccountName or uid or userPrincipalName": "Ejemplo: sAMNombreCuenta o uid o userNombrePrincipal",
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "Excedido el número de accesos de usuarios en tu licencia. Por favor, contacta con soporte para aumentar el número de accesos.",
"Exclude": "Excluir",
"Execute code": "",
"Execute code": "Ejecuar código",
"Execute code for analysis": "Ejecutar código para análisis",
"Executing **{{NAME}}**...": "Ejecutando **{{NAME}}**...",
"Expand": "Expandir",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exportar Todos los Chats (Todos los Usuarios)",
"Export chat (.json)": "Exportar chat (.json)",
"Export Chats": "Exportar Chats",
"Export Config to JSON File": "Exportar Configuración a archivo JSON",
"Export Config": "",
"Export Models": "Exportar Modelos",
"Export Presets": "Exportar Preajustes",
"Export Prompts": "Exportar Indicadores",
@@ -792,7 +792,7 @@
"Failed to copy link": "Fallo al copiar enlace",
"Failed to create API Key.": "Fallo al crear la Clave API.",
"Failed to delete note": "Fallo al eliminar nota",
"Failed to download image": "",
"Failed to download image": "Fallo al descargar imagen",
"Failed to extract content from the file: {{error}}": "Fallo al extraer el contenido del archivo: {{error}}",
"Failed to extract content from the file.": "Fallo al extraer el contenido del archivo.",
"Failed to fetch models": "Fallo al obtener los modelos",
@@ -810,7 +810,7 @@
"Failed to save connections": "Fallo al guardar las conexiones",
"Failed to save conversation": "Fallo al guardar la conversación",
"Failed to save models configuration": "Fallo al guardar la configuración de los modelos",
"Failed to unshare chat.": "",
"Failed to unshare chat.": "Fallo al descompartir chat.",
"Failed to update settings": "Fallo al actualizar los ajustes",
"Failed to update status": "Fallo al actualizar el estado",
"Failed to upload file.": "Fallo al subir el archivo.",
@@ -818,7 +818,7 @@
"Features Permissions": "Permisos de las Características",
"February": "Febrero",
"Feedback": "Opinión",
"Feedback Activity": "",
"Feedback Activity": "Actividad de las Opiniones",
"Feedback deleted successfully": "Opinión eliminada correctamente",
"Feedback Details": "Detalle de la Opinión",
"Feedback History": "Historia de la Opiniones",
@@ -828,7 +828,7 @@
"File added successfully.": "Archivo añadido correctamente.",
"File content updated successfully.": "Contenido del archivo actualizado correctamente.",
"File Context": "Contexto del Archivo",
"File deleted successfully.": "",
"File deleted successfully.": "Archivo borrado correctamente.",
"File Mode": "Modo de Archivo",
"File not found.": "Archivo no encontrado.",
"File removed successfully.": "Archivo eliminado correctamente.",
@@ -836,7 +836,7 @@
"File Upload": "Subir Archivo",
"File uploaded successfully": "Archivo subido correctamente",
"File uploaded!": "¡Archivo subido!",
"Filename": "",
"Filename": "Nombre del Archivo",
"Files": "Archivos",
"Filter": "Filtro",
"Filter is now globally disabled": "El filtro ahora está desactivado globalmente",
@@ -862,7 +862,7 @@
"Follow Up Generation Prompt": "Seguimiento de la Generación del Indicador",
"Follow-Up Auto-Generation": "Seguimiento Auto-Generación",
"Followed instructions perfectly": "Siguió las instrucciones perfectamente",
"for placeholders": "",
"for placeholders": "para marcadores de posición",
"Force OCR": "Forzar OCR",
"Force OCR on all pages of the PDF. This can lead to worse results if you have good text in your PDFs. Defaults to False.": "Forzar OCR en todas las páginas del PDF. Puede empeorar el resultado en PDFs que ya tengan una buena capa de texto. El valor predeterminado es desactivado (no forzar)",
"Forge new paths": "Forjar nuevos caminos",
@@ -897,13 +897,13 @@
"General": "General",
"Generate": "Generar",
"Generate an image": "Generar una imagen",
"Generate and edit images": "",
"Generate and edit images": "Generar y editar imágenes",
"Generate Message Pair": "Generar Par de Mensajes",
"Generated Image": "Imagen Generada",
"Generated images will appear here": "",
"Generated images will appear here": "Las imágenes generdas aparecerán aquí",
"Generating search query": "Generando consulta de búsqueda",
"Generating...": "Generando",
"Get current time and perform date/time calculations": "",
"Get current time and perform date/time calculations": "Obtener la hora actual y realizar cálculos de fecha y hora",
"Get information on {{name}} in the UI": "Obtener información sobre {{name}} en la IU",
"Get started": "Empezar",
"Get started with {{WEBUI_NAME}}": "Empezar con {{WEBUI_NAME}}",
@@ -934,16 +934,16 @@
"Height": "Altura",
"Hello, {{name}}": "Hola, {{name}}",
"Help": "Ayuda",
"Help the community discover great models": "",
"Help the community discover great models": "Ayuda a la comunidad a descubrir grandes modelos",
"Hex Color": "Color Hex",
"Hex Color - Leave empty for default color": "Color Hex - Deja vacío para el color predeterminado",
"Hidden": "",
"Hidden": "Oculto",
"Hide": "Esconder",
"Hide from Sidebar": "Ocultar Panel Lateral",
"Hide Model": "Ocultar Modelo",
"High": "Alto",
"High Contrast Mode": "Modo Alto Contraste",
"History": "",
"History": "Historial",
"Home": "Inicio",
"Host": "Host",
"How can I help you today?": "¿Cómo puedo ayudarte hoy?",
@@ -955,7 +955,7 @@
"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Aseguro que he leído y entiendo las implicaciones de mi acción. Soy consciente de los riesgos asociados con la ejecución de código arbitrario y he verificado la confiabilidad de la fuente.",
"ID": "ID",
"ID cannot contain \":\" or \"|\" characters": "ID no puede contener los caracteres \":\" o \"|\"",
"ID copied to clipboard": "",
"ID copied to clipboard": "ID copiado al portapapeles",
"iframe Sandbox Allow Forms": "iframe Sandbox Allow Forms",
"iframe Sandbox Allow Same Origin": "iframe Sandbox Allow Same Origin",
"Ignite curiosity": "Encender la curiosidad",
@@ -976,7 +976,7 @@
"Images": "Imágenes",
"Import": "Importar",
"Import Chats": "Importar Chats",
"Import Config from JSON File": "Importar Config desde Archivo JSON",
"Import Config": "",
"Import From Link": "Importar desde Enlace",
"Import Models": "Importar Modelos",
"Import Presets": "Importar Preajustes",
@@ -984,7 +984,7 @@
"Import successful": "Importación realizada correctamente",
"Import Tools": "Importar Herramientas",
"Important Update": "Actualización importante",
"Inactive": "",
"Inactive": "Inactivo",
"Include": "Incluir",
"Include `--api-auth` flag when running stable-diffusion-webui": "Incluir el señalizador `--api-auth` al ejecutar stable-diffusion-webui",
"Include `--api` flag when running stable-diffusion-webui": "Incluir el señalizador `--api` al ejecutar stable-diffusion-webui",
@@ -1066,15 +1066,15 @@
"Learn More": "Saber Más",
"Learn more about OpenAPI tool servers.": "Saber más sobre los servidores de herramientas OpenAPI",
"Learn more about Voxtral transcription.": "Saber más sobre la transcripción con Voxtral",
"Leave a public review for {{modelName}}": "",
"Leave empty for no compression": "Lejar vacío para no compresión",
"Leave a public review for {{modelName}}": "Dejar revisión pública de {{modelName}}",
"Leave empty for no compression": "Dejar vacío para no compresión",
"Leave empty for unlimited": "Dejar vacío para ilimitado",
"Leave empty to include all models from \"{{url}}\" endpoint": "Dejar vacío para incluir todos los modelos desde el endpoint \"{{url}}\"",
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Dejar vacío para incluir todos los modelos desde el endpoint \"{{url}}/api/tags\"",
"Leave empty to include all models from \"{{url}}/models\" endpoint": "Dejar vacío para incluir todos los modelos desde el endpoint \"{{url}}/models\"",
"Leave empty to include all models or select specific models": "Dejar vacío para incluir todos los modelos o Seleccionar modelos específicos",
"Leave empty to use first admin user": "Dejar vacío para utilizar el primer usuario administrador",
"Leave empty to use the default config, or enter a valid json (see https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)": "",
"Leave empty to use the default config, or enter a valid json (see https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)": "Déjelo vacío para usar la configuración predeterminada o ingresa un json válido (consulta https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)",
"Leave empty to use the default model (voxtral-mini-latest).": "Dejar vacío para usar el modo predeterminado (voxtral-mini-latest).",
"Leave empty to use the default prompt, or enter a custom prompt": "Dejar vacío para usar el indicador predeterminado, o Ingresar un indicador personalizado",
"Leave model field empty to use the default model.": "Dejar vacío el campo modelo para usar el modelo predeterminado.",
@@ -1086,7 +1086,7 @@
"Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "Limitar consultas de búsqueda simultáneas. 0 = ilimitado (predeterminado). Establécerlo en 1 para ejecución secuencial (recomendado para API con límites de velocidad estrictos, como la versión gratuita de Brave).",
"List": "Lista",
"Listening...": "Escuchando...",
"Live": "",
"Live": "En Directo",
"Llama.cpp": "Llama.cpp",
"LLMs can make mistakes. Verify important information.": "Los LLMs pueden cometer errores. Verifica la información importante.",
"Loader": "Cargador",
@@ -1105,7 +1105,7 @@
"Male": "Hombre",
"Manage": "Gestionar",
"Manage Direct Connections": "Gestionar Conexiones Directas",
"Manage Files": "",
"Manage Files": "Gestionar Archivos",
"Manage Models": "Gestionar Modelos",
"Manage Ollama": "Gestionar Ollama",
"Manage Ollama API Connections": "Gestionar Conexiones API de Ollama",
@@ -1128,7 +1128,7 @@
"MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "El soporte de MCP es experimental y su especificación cambia con frecuencia, lo que puede generar incompatibilidades. El equipo de Open WebUI mantiene directamente la compatibilidad con la especificación OpenAPI, lo que la convierte en la opción más fiable para la compatibilidad.",
"Medium": "Medio",
"Member removed successfully": "Miembro removido correctamente",
"members": "",
"members": "miembros",
"Members": "Miembros",
"Members added successfully": "Miembros añadidos correctamente",
"Memories": "Memorias",
@@ -1142,10 +1142,10 @@
"Merged Response": "Respuesta combinada",
"Message": "Mensaje",
"Message counts and response timestamps": "Recuento de mensajes y marcas de tiempo de la respuesta",
"Message counts are based on assistant responses.": "",
"Message counts are based on assistant responses.": "El recuento de mensajes está basado en las respuestas del asistente",
"Message rating should be enabled to use this feature": "Para usar esta función debe estar habilitada la calificación de mensajes",
"messages": "",
"Messages": "",
"messages": "mensajes",
"Messages": "Mensajes",
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Los mensajes que envíe después de la creación del enlace no se compartirán. Los usuarios con la URL del enlace podrán ver el chat compartido.",
"Microsoft OneDrive": "Microsoft OneDrive",
"Microsoft OneDrive (personal)": "Microsoft OneDrive (personal)",
@@ -1178,14 +1178,14 @@
"Model name already exists, please choose a different one": "Ese nombre de modelo ya existe, por favor elige otro diferente",
"Model Name is required.": "El Nombre de Modelo es requerido",
"Model names and usage frequency": "Nombres de modelos y frecuencia de uso",
"Model not found": "",
"Model not found": "Modelo no encontrado",
"Model not selected": "Modelo no seleccionado",
"Model Params": "Paráms Modelo",
"Model Permissions": "Permisos Modelo",
"Model responses or outputs": "Respuestas del modelo o salidas",
"Model unloaded successfully": "Modelo descargado correctamente",
"Model updated successfully": "Modelo actualizado correctamente",
"Model Usage": "",
"Model Usage": "Uso del Modelo",
"Model(s) do not support file upload": "Modelo/s no soportan subida de archivos",
"Modelfile Content": "Contenido del Modelfile",
"Models": "Modelos",
@@ -1198,7 +1198,7 @@
"Mojeek Search API Key": "Clave API de Mojeek Search",
"More": "Más",
"More Concise": "Más Conciso",
"More options": "",
"More options": "Más Opciones",
"More Options": "Más Opciones",
"Move": "Mover",
"Name": "Nombre",
@@ -1215,13 +1215,13 @@
"New Note": "Nueva Nota",
"New Password": "Nueva Contraseña",
"New Prompt": "Nuevo Indicador",
"New Skill": "",
"New Skill": "Nueva Habilidad",
"New Temporary Chat": "Nuevo Chat Temporal",
"New Tool": "Nueva Herramienta",
"New Webhook": "Nuevo Webhook",
"new-channel": "nuevo-canal",
"Next message": "Siguiente mensaje",
"No access grants. Private to you.": "",
"No access grants. Private to you.": "Sin acceso concedido. Privado para tí.",
"No activity data": "Sin datos de actividad",
"No authentication": "Sin Autentificación",
"No chats found": "No se encontró ningún chat",
@@ -1231,16 +1231,16 @@
"No content found": "No se encontró contenido",
"No content to speak": "No hay contenido para hablar",
"No conversation to save": "No hay conversación para guardar",
"No data": "",
"No data found": "",
"No data": "Sin datos",
"No data found": "No se encontró ningún dato",
"No distance available": "No hay distancia disponible",
"No expiration can pose security risks.": "No expiración puede poner la seguridad en riesgo.",
"No feedback found": "No se encontró ninguna opinión",
"No file selected": "No se seleccionó archivo",
"No files found": "",
"No files found": "No se encontraron archivos",
"No files in this knowledge base.": "Ningún archivo en esta base de conocimiento.",
"No functions found": "No se encontraron funciones",
"No history available": "",
"No history available": "No hay historial disponible",
"No HTML, CSS, or JavaScript content found.": "No se encontró contenido HTML, CSS, o JavaScript.",
"No inference engine with management support found": "No se encontró un motor de inferencia que soporte gestión",
"No knowledge bases found.": "No se encontraron bases de conocimiento",
@@ -1257,7 +1257,7 @@
"No results": "No se encontraron resultados",
"No results found": "No se encontraron resultados",
"No search query generated": "No se generó ninguna consulta de búsqueda",
"No skills found": "",
"No skills found": "No se encontraron habilidades",
"No source available": "No hay fuente disponible",
"No sources found": "No se han encontrado fuentes",
"No suggestion prompts": "Sin prompts sugeridos",
@@ -1316,7 +1316,7 @@
"Open modal to configure connection": "Abrir modal para configurar la conexión",
"Open Modal To Manage Floating Quick Actions": "Abrir Modal para Gestionar Acciones Rápidas Flotantes",
"Open Modal To Manage Image Compression": "Abrir Modal para Gestionar Compresión de Imagen",
"Open Model Selector": "",
"Open Model Selector": "Avrir Selector de Modelos",
"Open Settings": "Abrir Configuración",
"Open Sidebar": "Abrir Barra Lateral",
"Open User Profile Menu": "Abrir Menu de Perfiles de Usuario",
@@ -1336,19 +1336,20 @@
"OpenAPI": "OpenAPI",
"OpenAPI Spec": "Especif. OpenAPI",
"openapi.json URL or Path": "URL o Ruta a openapi.json",
"optional": "",
"optional": "opcional",
"Optional": "Opcional",
"or": "o",
"Ordered List": "Lista Ordenada",
"Organize your users": "Organiza tus usuarios",
"Other": "Otro",
"Output": "",
"OUTPUT": "SALIDA",
"Output format": "Formato de salida",
"Output Format": "Formato de Salida",
"Overview": "Vista General",
"page": "página",
"Page": "",
"Page mode creates one document per page. Single mode combines all pages into one document for better chunking across page boundaries.": "",
"Page": "Página",
"Page mode creates one document per page. Single mode combines all pages into one document for better chunking across page boundaries.": "El modo Página crea un documento por página. El modo Individual combina todas las páginas en un solo documento para una mejor segmentación entre páginas.",
"Paginate": "Paginar",
"Parameters": "Parámetros",
"Parent message not found": "Mensaje padre no encontrado",
@@ -1358,7 +1359,7 @@
"Paste Large Text as File": "Pegar el Texto Largo como Archivo",
"PDF document (.pdf)": "Documento PDF (.pdf)",
"PDF Extract Images (OCR)": "Extraer imágenes del PDF (OCR)",
"PDF Loader Mode": "",
"PDF Loader Mode": "Modo de Carga del PDF",
"pending": "pendiente",
"Pending": "Pendiente",
"Pending User Overlay Content": "Contenido de la SobreCapa Usuario Pendiente",
@@ -1418,13 +1419,13 @@
"Previous message": "Mensaje anterior",
"Private": "Privado",
"Private conversation between selected users": "Conversación privada entre l@s usuari@s seleccionados",
"Production version updated": "",
"Production version updated": "Versión de Producción actualizada",
"Profile": "Perfil",
"Prompt": "Indicador",
"Prompt Autocompletion": "Autocompletado del Indicador",
"Prompt Content": "Contenido del Indicador",
"Prompt created successfully": "Indicador creado exitosamente",
"Prompt Name": "",
"Prompt Name": "Nombre del Indicador",
"Prompt updated successfully": "Indicador actualizado correctamente",
"Prompts": "Indicadores",
"Prompts Access": "Acceso a Indicadores",
@@ -1478,6 +1479,7 @@
"Remove image": "Eliminar imagen",
"Remove Model": "Eliminar Modelo",
"Rename": "Renombrar",
"Render Markdown in Previews": "",
"Reorder Models": "Reordenar Modelos",
"Reply": "Responder",
"Reply in Thread": "Responder en Hilo",
@@ -1496,7 +1498,7 @@
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Las notificaciones de respuesta no pueden activarse ya que los permisos del sitio web han sido denegados. Por favor, comprueba la configuración de tu navegador para otorgar el permiso necesario.",
"Response splitting": "Particionado de Respuesta",
"Response Watermark": "Marca de Agua en Respuesta",
"Responses": "",
"Responses": "Respuestas",
"Result": "Resultado",
"RESULT": "Resultado",
"Retrieval": "Recuperación",
@@ -1524,13 +1526,13 @@
"Search": "Buscar",
"Search a model": "Buscar un Modelo",
"Search all emojis": "Buscar todos los emojis",
"Search and manage user memories": "",
"Search and view user chat history": "",
"Search and manage user memories": "Buscar y gestionar memorias del usuario",
"Search and view user chat history": "Buscr y ver historial de los chat del usuario",
"Search Base": "Busqueda Base",
"Search channels and channel messages": "",
"Search channels and channel messages": "Buscar canales y mensajes del canal",
"Search Chats": "Buscar Chats",
"Search Collection": "Buscar Colección",
"Search Files": "",
"Search Files": "Buscar archivos",
"Search Filters": "Buscar Filtros",
"search for archived chats": "buscar chats archivados",
"search for folders": "buscar carpetas",
@@ -1545,11 +1547,11 @@
"Search options": "Opciones de Búsqueda",
"Search Prompts": "Buscar Indicadores",
"Search Result Count": "Número de resultados de la búsqueda",
"Search Skills": "",
"Search Skills": "Buscar Habilidades",
"Search the internet": "Buscar en internet",
"Search the web and fetch URLs": "",
"Search the web and fetch URLs": "Buscar en la web y obtener URLs",
"Search Tools": "Buscar Herramientas",
"Search, view, and manage user notes": "",
"Search, view, and manage user notes": "Buscar, ver y gestionar notas del usuario",
"SearchApi API Key": "Clave API de SearchApi",
"SearchApi Engine": "Motor SearchApi",
"Searched {{count}} sites": "{{count}} sitios buscados",
@@ -1584,7 +1586,7 @@
"Select an embedding model engine": "Seleccionar un motor de modelos de incrustación",
"Select an engine": "Seleccionar un motor",
"Select an Ollama instance": "Seleccionar una instancia de Ollama",
"Select an option": "",
"Select an option": "Seleccionar una opción",
"Select an output format": "Seleccionar un formato de salida",
"Select dtype": "Seleccionar dtype",
"Select Engine": "Seleccionar Motor",
@@ -1598,7 +1600,7 @@
"Send": "Enviar",
"Send a Message": "Enviar un Mensaje",
"Send message": "Enviar Mensaje",
"Send now": "",
"Send now": "Enviar ahora",
"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "Envia en la solicitud de transmisión la opción: `{ include_usage: true }`.\nSi se activa, los proveedores que soporten esta función devolverán en la respuesta información de uso de los token.",
"September": "Septiembre",
"SerpApi API Key": "Clave API de SerpApi",
@@ -1609,7 +1611,7 @@
"Server connection verified": "Conexión al servidor verificada",
"Session": "Sesión",
"Set as default": "Establecer como Predeterminado",
"Set as Production": "",
"Set as Production": "Establecer como Producción",
"Set embedding model": "Establecer Modelo de Incrustación",
"Set embedding model (e.g. {{model}})": "Establecer Modelo para Incrustación (p.ej. {{model}})",
"Set reranking model (e.g. {{model}})": "Establecer Modelo para Reclasificación (p.ej. {{model}})",
@@ -1630,15 +1632,16 @@
"Settings saved successfully!": "¡Ajustes guardados correctamente!",
"Share": "Compartir",
"Share Chat": "Compartir Chat",
"Share link copied to clipboard.": "",
"Share link copied to clipboard.": "Enlace Compartido copiado al portapapeles",
"Share to Open WebUI Community": "Compartir con la Comunidad Open-WebUI",
"Share your background and interests": "Compartir tus antecedentes e intereses",
"Shared Chats": "",
"Shared Chats": "Chats Compartidos",
"Shared with you": "Compartido contigo",
"Sharing Permissions": "Permisos al Compartir",
"Show": "Mostrar",
"Show \"What's New\" modal on login": "Mostrar modal \"Qué hay de Nuevo\" al iniciar sesión",
"Show Admin Details in Account Pending Overlay": "Mostrar Detalles Admin en la sobrecapa de 'Cuenta Pendiente'",
"Show all ({{COUNT}} characters)": "",
"Show Files": "Mostrar Archivos",
"Show Formatting Toolbar": "Mostrar barra de herramientas de Formateo",
"Show image preview": "Mostrar previsualización de imagen",
@@ -1646,7 +1649,7 @@
"Show Shortcuts": "Mostrar Atajos de Teclado",
"Show your support!": "¡Muestra tu apoyo!",
"Showcased creativity": "Creatividad exhibida",
"Showing all messages (user + assistant) per user.": "",
"Showing all messages (user + assistant) per user.": "Mostrando todos los mensajes (usuario y asistente) del usuario.",
"Sign in": "Iniciar Sesión",
"Sign in to {{WEBUI_NAME}}": "Iniciar Sesión en {{WEBUI_NAME}}",
"Sign in to {{WEBUI_NAME}} with LDAP": "Iniciar Sesión en {{WEBUI_NAME}} con LDAP",
@@ -1655,17 +1658,19 @@
"Sign up to {{WEBUI_NAME}}": "Crear una Cuenta en {{WEBUI_NAME}}",
"Significantly improves accuracy by using an LLM to enhance tables, forms, inline math, and layout detection. Will increase latency. Defaults to False.": "Mejora significativamente la precisión al usar un LLM para optimizar tablas, formularios, cálculos en línea y la detección de diseño. Aumentará la latencia. El valor predeterminado es Falso.",
"Signing in to {{WEBUI_NAME}}": "Iniciando Sesión en {{WEBUI_NAME}}",
"Single": "",
"Single": "Individual",
"Sink List": "Plegar Lista",
"sk-1234": "sk-1234",
"Skill created successfully": "",
"Skill deleted successfully": "",
"Skill Description": "",
"Skill ID": "",
"Skill Name": "",
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skill created successfully": "Habilidad creada correctamente",
"Skill deleted successfully": "Habilidad borrada correctamente",
"Skill Description": "Descripción de la Habilidad",
"Skill ID": "ID de la Habilidad",
"Skill Name": "Nombre de la Habilidad",
"Skill updated successfully": "Habilidad actualiada correctamente",
"Skills": "Habilidades",
"Skills Access": "Acceso a Habilidades",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Evitar Caché",
"Skip the cache and re-run the inference. Defaults to False.": "Evitar caché y reiniciar la interfaz. Valor predeterminado Falso",
"Something went wrong :/": "Algo ha ido mal :/",
@@ -1772,7 +1777,7 @@
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Este canal fue creado el {{createdAt}}. Este es el comienzo del canal {{channelName}}.",
"This chat won't appear in history and your messages will not be saved.": "Este chat no aparecerá en el historial y los mensajes no se guardarán.",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Esto garantiza que sus valiosas conversaciones se guardan de forma segura en tu base de datos del servidor trasero (backend). ¡Gracias!",
"This feature is currently experimental and may not work as expected.": "",
"This feature is currently experimental and may not work as expected.": "Acualmente esta característica es experimental y puede no funcionar como se espera.",
"This feature is experimental and may be modified or discontinued without notice.": "Esta característica es experimental y podría ser modificada o discontinuada sin previo aviso.",
"This is a default user permission and will remain enabled.": "Este es un permiso predeterminado y se mantentrá activo ",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Esta es una característica experimental, por lo que puede no funcionar como se esperaba y está sujeta a cambios en cualquier momento.",
@@ -1793,11 +1798,11 @@
"Thought for {{DURATION}} seconds": "Pensando durante {{DURATION}} segundos",
"Thought for less than a second": "Pensando durante menos de un segundo",
"Thread": "Hilo",
"Thumbs up/down ratings from users on model responses": "",
"Thumbs up/down ratings from users on model responses": "Calificación (pulgares arriba/abajo) de los usuarios en las respuestas del modelo",
"Tika": "Tika",
"Tika Server URL required.": "URL del Servidor Tika necesaria",
"Tiktoken": "Tiktoken",
"Time & Calculation": "",
"Time & Calculation": "Tiempo y Cálculo",
"Timeout": "TimeOut",
"Title": "Título",
"Title Auto-Generation": "AutoGeneración de Títulos",
@@ -1810,7 +1815,7 @@
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Para acceder a WebUI, por favor contacte con Admins. Los administradores pueden gestionar los estados de los usuarios esde el panel de administración.",
"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Para adjuntar la base de conocimientos aquí, primero añadirla a \"Conocimiento\" en el área de trabajo.",
"To learn more about available endpoints, visit our documentation.": "Para aprender más sobre los endpoints disponibles, visite nuestra documentación.",
"To select skills here, add them to the \"Skills\" workspace first.": "",
"To select skills here, add them to the \"Skills\" workspace first.": "Para seleccionar habilidades aquí, primero añadelas a \"Habilidades\" en el áreas de trabajo.",
"To select toolkits here, add them to the \"Tools\" workspace first.": "Para seleccionar herramientas aquí, primero añadelas a \"Herramientas\" en el área de trabajo.",
"Toast notifications for new updates": "Notificaciones emergentes para nuevas actualizaciones",
"Today": "Hoy",
@@ -1818,9 +1823,9 @@
"Toggle Sidebar": "Des/Plegar la Barra Lateral",
"Toggle whether current connection is active.": "Alternar si la conexión actual está activa",
"Token": "Token",
"Token counts are estimates and may not reflect actual API usage": "",
"tokens": "",
"Tokens": "",
"Token counts are estimates and may not reflect actual API usage": "El recuento de tokens es estimado y puede diferir del uso real de la API",
"tokens": "tokens",
"Tokens": "Tokens",
"Too verbose": "Demasiado detallado",
"Tool created successfully": "Herramienta creada correctamente",
"Tool deleted successfully": "Herramienta eliminada correctamente",
@@ -1864,7 +1869,7 @@
"Unlock mysteries": "Desbloquear misterios",
"Unpin": "Desfijar",
"Unravel secrets": "Desentrañar secretos",
"Unshare Chat": "",
"Unshare Chat": "Descompartir Chat",
"Unsupported file type.": "Tipo de archivo no soportado",
"Untagged": "Sin Etiqueta",
"Untitled": "Sin Título",
@@ -1893,7 +1898,7 @@
"URL is required": "La URL es requerida",
"URL Mode": "Modo URL",
"Usage": "Uso",
"Use": "",
"Use": "Usar",
"Use '#' in the prompt input to load and include your knowledge.": "Utilizar '#' en el indicador para cargar e incluir tu conocimiento.",
"Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "Usar el endpoint /v1/chat/completions en vez de /v1/audio/transcriptions para una (posible) mayor precisión.",
"Use Chat Completions API": "Usar Chat Completions",
@@ -1903,7 +1908,7 @@
"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "Usar el proxy asignado en las variables del entorno http_proxy y/o https_proxy para extraer contenido",
"user": "usuario",
"User": "Usuario",
"User Activity": "",
"User Activity": "Actividad del Usuario",
"User Groups": "Grupos de Usuarios",
"User location successfully retrieved.": "Ubicación de usuario obtenida correctamente.",
"User menu": "Menu de Usuario",
@@ -1928,11 +1933,11 @@
"Verify SSL Certificate": "Verificar Certificado SSL",
"Version": "Versión",
"Version {{selectedVersion}} of {{totalVersions}}": "Versión {{selectedVersion}} de {{totalVersions}}",
"Version deleted": "",
"Version deleted": "Versión eliminada",
"View Replies": "Ver Respuestas",
"View Result from **{{NAME}}**": "Ver Resultado desde **{{NAME}}**",
"Visibility": "Visibilidad",
"Visible": "",
"Visible": "Visible",
"Visible to all users": "Visible para todos los usuarios",
"Vision": "Visión",
"Voice": "Voz",
@@ -1988,9 +1993,9 @@
"Yacy Username": "Usuario de Yacy",
"Yahoo": "Yahoo",
"Yandex": "Yandex",
"Yandex Web Search API Key": "",
"Yandex Web Search config": "",
"Yandex Web Search URL": "",
"Yandex Web Search API Key": "Clave API de la Búsqueda Web de Yandex",
"Yandex Web Search config": "Confoguración de la Búsqueda Web de Yantex",
"Yandex Web Search URL": "URL de la Búsqueda Web de Yandex",
"Yesterday": "Ayer",
"Yesterday at {{LOCALIZED_TIME}}": "Ayer a las {{LOCALIZED_TIME}}",
"You": "Tu",
@@ -2000,15 +2005,15 @@
"You cannot upload an empty file.": "No puedes subir un archivo vacío.",
"You do not have permission to edit this model": "No tienes permiso para editar este modelo",
"You do not have permission to edit this prompt.": "No tienes permiso para editar este indicador",
"You do not have permission to edit this skill.": "",
"You do not have permission to edit this tool": "",
"You do not have permission to make this public": "",
"You do not have permission to edit this skill.": "No tienes permiso para editar esta habilidad",
"You do not have permission to edit this tool": "No tienes permiso para editar esta herramienta",
"You do not have permission to make this public": "No tienes permiso para hacer público esto",
"You do not have permission to send messages in this channel.": "No tienes permiso para enviar mensajes en este canal",
"You do not have permission to send messages in this thread.": "No tienes permiso para enviar mensajes en este hilo",
"You do not have permission to upload files to this knowledge base.": "No tienes permiso para subir archivos a esta base de conocimiento.",
"You do not have permission to upload files.": "No tienes permiso para subir archivos.",
"You have no archived conversations.": "No tienes conversaciones archivadas.",
"You have no shared conversations.": "",
"You have no shared conversations.": "No tienes conversaciones compartidas",
"You have shared this chat": "Has compartido esta conversación",
"You're a helpful assistant.": "Eres un asistente atento, amable y servicial.",
"You're now logged in.": "Has iniciado sesión.",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Luba kõne",
"Allow Chat Controls": "Luba vestluse kontrollnupud",
"Allow Chat Delete": "Luba vestluse kustutamine",
"Allow Chat Deletion": "Luba vestluse kustutamine",
"Allow Chat Edit": "Luba vestluse muutmine",
"Allow Chat Export": "Luba vestluse eksport",
"Allow Chat Params": "Luba vestluse parameetrid",
@@ -346,6 +345,7 @@
"Completions": "Lõpetamised",
"Compress Images in Channels": "Tihenda pildid kanalites",
"Concurrent Requests": "Samaaegsed päringud",
"Config": "",
"Config imported successfully": "Seadistus edukalt imporditud",
"Configure": "Konfigureeri",
"Confirm": "Kinnita",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Ekspordi kõik vestlused (kõik kasutajad)",
"Export chat (.json)": "Ekspordi vestlus (.json)",
"Export Chats": "Ekspordi vestlused",
"Export Config to JSON File": "Ekspordi seadistus JSON-failina",
"Export Config": "",
"Export Models": "",
"Export Presets": "Ekspordi eelseadistused",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Impordi",
"Import Chats": "Impordi vestlused",
"Import Config from JSON File": "Impordi seadistus JSON-failist",
"Import Config": "",
"Import From Link": "Impordi lingist",
"Import Models": "",
"Import Presets": "Impordi eelseadistused",
@@ -1342,6 +1342,7 @@
"Ordered List": "Ordered List",
"Organize your users": "Korraldage oma kasutajad",
"Other": "Muu",
"Output": "",
"OUTPUT": "VÄLJUND",
"Output format": "Väljundformaat",
"Output Format": "Output Format",
@@ -1478,6 +1479,7 @@
"Remove image": "Eemalda pilt",
"Remove Model": "Eemalda mudel",
"Rename": "Nimeta ümber",
"Render Markdown in Previews": "",
"Reorder Models": "Muuda mudelite järjekorda",
"Reply": "Reply",
"Reply in Thread": "Vasta lõimes",
@@ -1638,6 +1640,7 @@
"Show": "Näita",
"Show \"What's New\" modal on login": "Näita \"Mis on uut\" modaalakent sisselogimisel",
"Show Admin Details in Account Pending Overlay": "Näita administraatori üksikasju konto ootel kattekihil",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "Kuva Formatting Toolbar",
"Show image preview": "Kuva pilt preview",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Skip Vahemälu",
"Skip the cache and re-run the inference. Defaults to False.": "Skip the vahemälu ja re-run the inference. Defaults kuni False.",
"Something went wrong :/": "Something went wrong :/",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "Baimendu Txata Ezabatzea",
"Allow Chat Deletion": "Baimendu Txata Ezabatzea",
"Allow Chat Edit": "Baimendu Txata Editatzea",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "Osatzeak",
"Compress Images in Channels": "",
"Concurrent Requests": "Eskari Konkurrenteak",
"Config": "",
"Config imported successfully": "",
"Configure": "Konfiguratu",
"Confirm": "Berretsi",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Esportatu Txat Guztiak (Erabiltzaile Guztiak)",
"Export chat (.json)": "Esportatu txata (.json)",
"Export Chats": "Esportatu Txatak",
"Export Config to JSON File": "Esportatu Konfigurazioa JSON Fitxategira",
"Export Config": "",
"Export Models": "",
"Export Presets": "Esportatu Aurrezarpenak",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Inportatu Txatak",
"Import Config from JSON File": "Inportatu Konfigurazioa JSON Fitxategitik",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "Inportatu Aurrezarpenak",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Antolatu zure erabiltzaileak",
"Other": "Bestelakoa",
"Output": "",
"OUTPUT": "IRTEERA",
"Output format": "Irteera formatua",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Kendu modeloa",
"Rename": "Berrizendatu",
"Render Markdown in Previews": "",
"Reorder Models": "Berrantolatu modeloak",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "Erakutsi",
"Show \"What's New\" modal on login": "Erakutsi \"Berritasunak\" modala saioa hastean",
"Show Admin Details in Account Pending Overlay": "Erakutsi administratzaile xehetasunak kontu zain geruzan",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "اجازه تماس",
"Allow Chat Controls": "اجازه کنترل\u200cهای گفتگو",
"Allow Chat Delete": "اجازه حذف گفتگو",
"Allow Chat Deletion": "اجازهٔ حذف گفتگو",
"Allow Chat Edit": "اجازه ویرایش گفتگو",
"Allow Chat Export": "مجاز کردن خروجی گرفتن از چت",
"Allow Chat Params": "مجاز کردن پارامترهای چت",
@@ -346,6 +345,7 @@
"Completions": "تکمیل\u200cها",
"Compress Images in Channels": "فشرده\u200cسازی تصاویر در کانال\u200cها",
"Concurrent Requests": "درخواست های همزمان",
"Config": "",
"Config imported successfully": "پیکربندی با موفقیت وارد شد",
"Configure": "پیکربندی",
"Confirm": "تایید",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "خروجی گرفتن همه گفتگو\u200cها (همه کاربران)",
"Export chat (.json)": "خروجی گرفتن گفتگو (json)",
"Export Chats": "خروجی گرفتن گفتگوها",
"Export Config to JSON File": "برون\u200cریزی پیکربندی به پروندهٔ JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "برون\u200cریزی پیش\u200cتنظیم\u200cها",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "وارد کردن",
"Import Chats": "درون\u200cریزی گفتگوها",
"Import Config from JSON File": "درون\u200cریزی از پروندهٔ JSON",
"Import Config": "",
"Import From Link": "وارد کردن از لینک",
"Import Models": "",
"Import Presets": "درون\u200cریزی پیش\u200cتنظیم\u200cها",
@@ -1342,6 +1342,7 @@
"Ordered List": "لیست شماره\u200cگذاری شده",
"Organize your users": "کاربران خود را سازماندهی کنید",
"Other": "دیگر",
"Output": "",
"OUTPUT": "خروجی",
"Output format": "قالب خروجی",
"Output Format": "قالب خروجی",
@@ -1478,6 +1479,7 @@
"Remove image": "حذف تصویر",
"Remove Model": "حذف مدل",
"Rename": "تغییر نام",
"Render Markdown in Previews": "",
"Reorder Models": "ترتیب مجدد مدل\u200cها",
"Reply": "پاسخ",
"Reply in Thread": "پاسخ در رشته",
@@ -1638,6 +1640,7 @@
"Show": "نمایش",
"Show \"What's New\" modal on login": "نمایش مودال \"موارد جدید\" هنگام ورود",
"Show Admin Details in Account Pending Overlay": "نمایش جزئیات مدیر در پوشش حساب در انتظار",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "نمایش نوار ابزار قالب\u200cبندی",
"Show image preview": "نمایش پیش\u200cنمایش تصویر",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "رد کردن کش",
"Skip the cache and re-run the inference. Defaults to False.": "کش را رد کرده و استنتاج را مجدداً اجرا کنید. پیش\u200cفرض: False.",
"Something went wrong :/": "مشکلی پیش آمد :/",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Salli puhelut",
"Allow Chat Controls": "Salli keskustelujen hallinta",
"Allow Chat Delete": "Salli keskustelujen poisto",
"Allow Chat Deletion": "Salli keskustelujen poisto",
"Allow Chat Edit": "Salli keskustelujen muokkaus",
"Allow Chat Export": "Salli keskustelujen vienti",
"Allow Chat Params": "Salli keskustelujen parametrit",
@@ -346,6 +345,7 @@
"Completions": "Täydennykset",
"Compress Images in Channels": "Pakkaa kuvat kanavissa",
"Concurrent Requests": "Samanaikaiset pyynnöt",
"Config": "",
"Config imported successfully": "Määritysten tuonti onnistui",
"Configure": "Määritä",
"Confirm": "Vahvista",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Vie kaikki keskustelut (kaikki käyttäjät)",
"Export chat (.json)": "Vie keskustelu (.json)",
"Export Chats": "Vie keskustelut",
"Export Config to JSON File": "Vie asetukset JSON-tiedostoon",
"Export Config": "",
"Export Models": "Vie mallit",
"Export Presets": "Vie esiasetukset",
"Export Prompts": "Vie kehoitteet",
@@ -976,7 +976,7 @@
"Images": "Kuvat",
"Import": "Tuo",
"Import Chats": "Tuo keskustelut",
"Import Config from JSON File": "Tuo asetukset JSON-tiedostosta",
"Import Config": "",
"Import From Link": "Tuo verkko-osoitteesta",
"Import Models": "Tuo mallit",
"Import Presets": "Tuo esiasetuksia",
@@ -1342,6 +1342,7 @@
"Ordered List": "Järjestetty lista",
"Organize your users": "Järjestä käyttäjäsi",
"Other": "Muu",
"Output": "",
"OUTPUT": "TULOSTE",
"Output format": "Tulosteen muoto",
"Output Format": "Tulosteen muoto",
@@ -1478,6 +1479,7 @@
"Remove image": "Poista kuva",
"Remove Model": "Poista malli",
"Rename": "Nimeä uudelleen",
"Render Markdown in Previews": "",
"Reorder Models": "Uudelleenjärjestä malleja",
"Reply": "Vastaa",
"Reply in Thread": "Vastaa ketjussa",
@@ -1638,6 +1640,7 @@
"Show": "Näytä",
"Show \"What's New\" modal on login": "Näytä \"Mitä uutta\" -modaali kirjautumisen yhteydessä",
"Show Admin Details in Account Pending Overlay": "Näytä ylläpitäjän tiedot odottavan tilin päällä",
"Show all ({{COUNT}} characters)": "",
"Show Files": "Näytä tiedostot",
"Show Formatting Toolbar": "Näytä muotoilupalkki",
"Show image preview": "Näytä kuvan esikatselu",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Ohita välimuisti",
"Skip the cache and re-run the inference. Defaults to False.": "Ohita välimuisti ja suorita päätelmä uudelleen. Oletusarvo ei käytössä.",
"Something went wrong :/": "Jokin meni pieleen :/",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Autoriser les appels",
"Allow Chat Controls": "Autoriser les contrôles de la conversation",
"Allow Chat Delete": "Autoriser la suppression de la conversation",
"Allow Chat Deletion": "Autoriser la suppression de l'historique de la conversation",
"Allow Chat Edit": "Autoriser la modification de la conversation",
"Allow Chat Export": "Autoriser l'exportation de la conversation",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "Complétions",
"Compress Images in Channels": "",
"Concurrent Requests": "Demandes concurrentes",
"Config": "",
"Config imported successfully": "",
"Configure": "Configurer",
"Confirm": "Confirmer",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exporter toutes les conversations (de tous les utilisateurs)",
"Export chat (.json)": "Exporter la conversation (.json)",
"Export Chats": "Exporter les conversations",
"Export Config to JSON File": "Exporter la configuration vers un fichier JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "Exporter les préréglages",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Importer",
"Import Chats": "Importer les conversations",
"Import Config from JSON File": "Importer la configuration depuis un fichier JSON",
"Import Config": "",
"Import From Link": "Importer depuis le lien",
"Import Models": "",
"Import Presets": "Importer les préréglages",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Organisez vos utilisateurs",
"Other": "Autre",
"Output": "",
"OUTPUT": "SORTIE",
"Output format": "Format de sortie",
"Output Format": "Format de sortie",
@@ -1478,6 +1479,7 @@
"Remove image": "Retirer l'image",
"Remove Model": "Retirer le modèle",
"Rename": "Renommer",
"Render Markdown in Previews": "",
"Reorder Models": "Réorganiser les modèles",
"Reply": "",
"Reply in Thread": "Répondre dans le fil de discussion",
@@ -1639,6 +1641,7 @@
"Show": "Afficher",
"Show \"What's New\" modal on login": "Afficher la fenêtre modale \"Quoi de neuf\" lors de la connexion",
"Show Admin Details in Account Pending Overlay": "Afficher les coordonnées de l'administrateur aux comptes en attente",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "Afficher l'aperçu de l'image",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Ne pas utiliser le cache",
"Skip the cache and re-run the inference. Defaults to False.": "Ne pas utiliser le cache et re executer l'inférence. Par defaut à False",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Autoriser les appels",
"Allow Chat Controls": "Autoriser les contrôles de la conversation",
"Allow Chat Delete": "Autoriser la suppression de la conversation",
"Allow Chat Deletion": "Autoriser la suppression de l'historique de la conversation",
"Allow Chat Edit": "Autoriser la modification de la conversation",
"Allow Chat Export": "Autoriser l'exportation de la conversation",
"Allow Chat Params": "Autoriser les paramètres de discussion",
@@ -346,6 +345,7 @@
"Completions": "Complétions",
"Compress Images in Channels": "Compresser les images dans les canaux",
"Concurrent Requests": "Demandes concurrentes",
"Config": "",
"Config imported successfully": "Configuration importée avec succès",
"Configure": "Configurer",
"Confirm": "Confirmer",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exporter toutes les conversations (de tous les utilisateurs)",
"Export chat (.json)": "Exporter la conversation (.json)",
"Export Chats": "Exporter les conversations",
"Export Config to JSON File": "Exporter la configuration vers un fichier JSON",
"Export Config": "",
"Export Models": "Exporter les modèles",
"Export Presets": "Exporter les préréglages",
"Export Prompts": "Exporter les prompts",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "Importer",
"Import Chats": "Importer les conversations",
"Import Config from JSON File": "Importer la configuration depuis un fichier JSON",
"Import Config": "",
"Import From Link": "Importer depuis le lien",
"Import Models": "Importer les modèles",
"Import Presets": "Importer les préréglages",
@@ -1342,6 +1342,7 @@
"Ordered List": "Liste ordonnéee",
"Organize your users": "Organisez vos utilisateurs",
"Other": "Autre",
"Output": "",
"OUTPUT": "SORTIE",
"Output format": "Format de sortie",
"Output Format": "Format de sortie",
@@ -1478,6 +1479,7 @@
"Remove image": "Retirer l'image",
"Remove Model": "Retirer le modèle",
"Rename": "Renommer",
"Render Markdown in Previews": "",
"Reorder Models": "Réorganiser les modèles",
"Reply": "",
"Reply in Thread": "Répondre dans le fil de discussion",
@@ -1639,6 +1641,7 @@
"Show": "Afficher",
"Show \"What's New\" modal on login": "Afficher la fenêtre modale \"Quoi de neuf\" lors de la connexion",
"Show Admin Details in Account Pending Overlay": "Afficher les coordonnées de l'administrateur aux comptes en attente",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "Afficher la barre d'outils de formatage",
"Show image preview": "Afficher l'aperçu de l'image",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Ne pas utiliser le cache",
"Skip the cache and re-run the inference. Defaults to False.": "Ne pas utiliser le cache et re executer l'inférence. Par defaut à False",
"Something went wrong :/": "Quelque chose s'est mal passé :/",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "Permitir Control dos Chats",
"Allow Chat Delete": "Permitir Borrar Chat",
"Allow Chat Deletion": "Permitir Borrar Chats",
"Allow Chat Edit": "Pemritir Editar Chat",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "Respostas autoxeradas",
"Compress Images in Channels": "",
"Concurrent Requests": "Solicitudes simultáneas",
"Config": "",
"Config imported successfully": "",
"Configure": "Configurar",
"Confirm": "Confirmar",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Exportar todos os chats (Todos os usuarios)",
"Export chat (.json)": "Exportar chat (.json)",
"Export Chats": "Exportar Chats",
"Export Config to JSON File": "Exportar configuración a Arquivo JSON",
"Export Config": "",
"Export Models": "",
"Export Presets": "Exportar ajustes preestablecidos",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Importar chats",
"Import Config from JSON File": "Importar configuración desde Arquivo JSON",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "Importar ajustes preestablecidos",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Organiza os teus usuarios",
"Other": "Outro",
"Output": "",
"OUTPUT": "SAIDA",
"Output format": "Formato de saida",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Eliminar modelo",
"Rename": "Renombrar",
"Render Markdown in Previews": "",
"Reorder Models": "Reordenar modelos",
"Reply": "",
"Reply in Thread": "Responder no hilo",
@@ -1638,6 +1640,7 @@
"Show": "Mostrar",
"Show \"What's New\" modal on login": "Mostrar modal \"Qué hay de novo\" al iniciar sesión",
"Show Admin Details in Account Pending Overlay": "Mostrar detalles de administración na capa de espera da conta",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "אפשר מחיקת צ'אט",
"Allow Chat Edit": "אפשר עריכת צ'אט",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "בקשות בו-זמניות",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "ייצוא כל הצ'אטים (כל המשתמשים)",
"Export chat (.json)": "",
"Export Chats": "ייצוא צ'אטים",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "יבוא צ'אטים",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "אחר",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "הסר מודל",
"Rename": "שנה שם",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1639,6 +1641,7 @@
"Show": "הצג",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "चैट हटाने की अनुमति दें",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "समवर्ती अनुरोध",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "सभी चैट निर्यात करें (सभी उपयोगकर्ताओं की)",
"Export chat (.json)": "",
"Export Chats": "चैट निर्यात करें",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "चैट आयात करें",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "अन्य",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "मोडेल हटाएँ",
"Rename": "नाम बदलें",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1638,6 +1640,7 @@
"Show": "दिखाओ",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "Dopusti brisanje razgovora",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "Istodobni zahtjevi",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Izvoz svih razgovora (svi korisnici)",
"Export chat (.json)": "Izvoz četa (.json)",
"Export Chats": "Izvoz razgovora",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Uvoz razgovora",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "Ostalo",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Ukloni model",
"Rename": "Preimenuj",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1639,6 +1641,7 @@
"Show": "Pokaži",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1666,6 +1669,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "Csevegésvezérlők engedélyezése",
"Allow Chat Delete": "Csevegés törlésének engedélyezése",
"Allow Chat Deletion": "Beszélgetések törlésének engedélyezése",
"Allow Chat Edit": "Csevegés szerkesztésének engedélyezése",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "Kiegészítések",
"Compress Images in Channels": "",
"Concurrent Requests": "Párhuzamos kérések",
"Config": "",
"Config imported successfully": "",
"Configure": "Konfigurálás",
"Confirm": "Megerősítés",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Minden beszélgetés exportálása (minden felhasználó)",
"Export chat (.json)": "Beszélgetés exportálása (.json)",
"Export Chats": "Beszélgetések exportálása",
"Export Config to JSON File": "Konfiguráció exportálása JSON fájlba",
"Export Config": "",
"Export Models": "",
"Export Presets": "Előre beállított exportálás",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Beszélgetések importálása",
"Import Config from JSON File": "Konfiguráció importálása JSON fájlból",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "Előre beállított importálás",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "Szervezd meg a felhasználóidat",
"Other": "Egyéb",
"Output": "",
"OUTPUT": "KIMENET",
"Output format": "Kimeneti formátum",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Modell eltávolítása",
"Rename": "Átnevezés",
"Render Markdown in Previews": "",
"Reorder Models": "Modellek átrendezése",
"Reply": "",
"Reply in Thread": "Válasz szálban",
@@ -1638,6 +1640,7 @@
"Show": "Mutat",
"Show \"What's New\" modal on login": "\"Mi újság\" modal megjelenítése bejelentkezéskor",
"Show Admin Details in Account Pending Overlay": "Admin részletek megjelenítése a függő fiók átfedésben",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "",
"Allow Chat Controls": "",
"Allow Chat Delete": "",
"Allow Chat Deletion": "Izinkan Penghapusan Obrolan",
"Allow Chat Edit": "",
"Allow Chat Export": "",
"Allow Chat Params": "",
@@ -346,6 +345,7 @@
"Completions": "",
"Compress Images in Channels": "",
"Concurrent Requests": "Permintaan Bersamaan",
"Config": "",
"Config imported successfully": "",
"Configure": "",
"Confirm": "Konfirmasi",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Ekspor Semua Obrolan (Semua Pengguna)",
"Export chat (.json)": "Ekspor obrolan (.json)",
"Export Chats": "Ekspor Obrolan",
"Export Config to JSON File": "",
"Export Config": "",
"Export Models": "",
"Export Presets": "",
"Export Prompts": "",
@@ -976,7 +976,7 @@
"Images": "",
"Import": "",
"Import Chats": "Impor Obrolan",
"Import Config from JSON File": "",
"Import Config": "",
"Import From Link": "",
"Import Models": "",
"Import Presets": "",
@@ -1342,6 +1342,7 @@
"Ordered List": "",
"Organize your users": "",
"Other": "Lainnya",
"Output": "",
"OUTPUT": "",
"Output format": "",
"Output Format": "",
@@ -1478,6 +1479,7 @@
"Remove image": "",
"Remove Model": "Hapus Model",
"Rename": "Ganti nama",
"Render Markdown in Previews": "",
"Reorder Models": "",
"Reply": "",
"Reply in Thread": "",
@@ -1637,6 +1639,7 @@
"Show": "Tampilkan",
"Show \"What's New\" modal on login": "",
"Show Admin Details in Account Pending Overlay": "Tampilkan Detail Admin di Hamparan Akun Tertunda",
"Show all ({{COUNT}} characters)": "",
"Show Files": "",
"Show Formatting Toolbar": "",
"Show image preview": "",
@@ -1664,6 +1667,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "",
"Skip the cache and re-run the inference. Defaults to False.": "",
"Something went wrong :/": "",
+8 -3
View File
@@ -98,7 +98,6 @@
"Allow Call": "Ceadaigh Glao",
"Allow Chat Controls": "Ceadaigh Rialuithe Comhrá",
"Allow Chat Delete": "Ceadaigh Comhrá a Scriosadh",
"Allow Chat Deletion": "Cead Scriosadh Comhrá",
"Allow Chat Edit": "Ceadaigh Eagarthóireacht Comhrá",
"Allow Chat Export": "Ceadaigh Easpórtáil Comhrá",
"Allow Chat Params": "Ceadaigh Paraiméadair Comhrá",
@@ -346,6 +345,7 @@
"Completions": "Críochnaithe",
"Compress Images in Channels": "Comhbhrúigh Íomhánna i gCainéil",
"Concurrent Requests": "Iarrataí Comhthéime",
"Config": "",
"Config imported successfully": "Cumraíocht allmhairithe go rathúil",
"Configure": "Cumraigh",
"Confirm": "Deimhnigh",
@@ -769,7 +769,7 @@
"Export All Chats (All Users)": "Easpórtáil gach comhrá (Gach Úsáideoir)",
"Export chat (.json)": "Easpórtáil comhrá (.json)",
"Export Chats": "Comhráite Easpórtá",
"Export Config to JSON File": "Easpórtáil Cumraíocht chuig Comhad JSON",
"Export Config": "",
"Export Models": "Samhlacha Easpórtála",
"Export Presets": "Easpórtáil Gach Comhrá Cartlainne",
"Export Prompts": "Leideanna Easpórtála",
@@ -976,7 +976,7 @@
"Images": "Íomhánna",
"Import": "Iompórtáil",
"Import Chats": "Comhráite iompórtá",
"Import Config from JSON File": "Cumraíocht Iompórtáil ó Chomhad JSON",
"Import Config": "",
"Import From Link": "Iompórtáil Ó Nasc",
"Import Models": "Iompórtáil Samhlacha",
"Import Presets": "Réamhshocruithe Iompórtáil",
@@ -1342,6 +1342,7 @@
"Ordered List": "Liosta Ordaithe",
"Organize your users": "Eagraigh do chuid úsáideoirí",
"Other": "Eile",
"Output": "",
"OUTPUT": "ASCHUR",
"Output format": "Formáid aschuir",
"Output Format": "Formáid Aschuir",
@@ -1478,6 +1479,7 @@
"Remove image": "Bain íomhá",
"Remove Model": "Bain an tSamhail",
"Rename": "Athainmnigh",
"Render Markdown in Previews": "",
"Reorder Models": "Athordú na Samhlacha",
"Reply": "Freagra",
"Reply in Thread": "Freagra i Snáithe",
@@ -1638,6 +1640,7 @@
"Show": "Taispeáin",
"Show \"What's New\" modal on login": "Taispeáin módúil \"Cad atá Nua\" ar logáil isteach",
"Show Admin Details in Account Pending Overlay": "Taispeáin Sonraí Riaracháin sa Chuntas ar Feitheamh Forleagan",
"Show all ({{COUNT}} characters)": "",
"Show Files": "Taispeáin Comhaid",
"Show Formatting Toolbar": "Taispeáin Barra Uirlisí Formáidithe",
"Show image preview": "Taispeáin réamhamharc íomhá",
@@ -1665,6 +1668,8 @@
"Skill updated successfully": "",
"Skills": "",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
"Skip Cache": "Seachain an Taisce",
"Skip the cache and re-run the inference. Defaults to False.": "Seachain an taisce agus athrith an tátal. Réamhshocrú Bréagach.",
"Something went wrong :/": "Chuaigh rud éigin mícheart :/",

Some files were not shown because too many files have changed in this diff Show More