mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-12 04:15:25 +00:00
Merge branch 'open-webui:main' into universal_file_deletion
This commit is contained in:
commit
2f53477de1
156 changed files with 6122 additions and 3198 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
|
|
@ -13,6 +13,8 @@ body:
|
||||||
|
|
||||||
- **Before submitting a bug report**: Please check the [Issues](https://github.com/open-webui/open-webui/issues) and [Discussions](https://github.com/open-webui/open-webui/discussions) sections to see if a similar issue has already been reported. If unsure, start a discussion first, as this helps us efficiently focus on improving the project. Duplicates may be closed without notice. **Please search for existing issues and discussions.**
|
- **Before submitting a bug report**: Please check the [Issues](https://github.com/open-webui/open-webui/issues) and [Discussions](https://github.com/open-webui/open-webui/discussions) sections to see if a similar issue has already been reported. If unsure, start a discussion first, as this helps us efficiently focus on improving the project. Duplicates may be closed without notice. **Please search for existing issues and discussions.**
|
||||||
|
|
||||||
|
- Check for opened, **but also for (recently) CLOSED issues** as the issue you are trying to report **might already have been fixed!**
|
||||||
|
|
||||||
- **Respectful collaboration**: Open WebUI is a volunteer-driven project with a single maintainer and contributors who also have full-time jobs. Please be constructive and respectful in your communication.
|
- **Respectful collaboration**: Open WebUI is a volunteer-driven project with a single maintainer and contributors who also have full-time jobs. Please be constructive and respectful in your communication.
|
||||||
|
|
||||||
- **Contributing**: If you encounter an issue, consider submitting a pull request or forking the project. We prioritize preventing contributor burnout to maintain Open WebUI's quality.
|
- **Contributing**: If you encounter an issue, consider submitting a pull request or forking the project. We prioritize preventing contributor burnout to maintain Open WebUI's quality.
|
||||||
|
|
|
||||||
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
**Before submitting, make sure you've checked the following:**
|
**Before submitting, make sure you've checked the following:**
|
||||||
|
|
||||||
- [ ] **Target branch:** Please verify that the pull request targets the `dev` branch.
|
- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. Not targeting the `dev` branch may lead to immediate closure of the PR.
|
||||||
- [ ] **Description:** Provide a concise description of the changes made in this pull request.
|
- [ ] **Description:** Provide a concise description of the changes made in this pull request.
|
||||||
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
|
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
|
||||||
- [ ] **Documentation:** Have you updated relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs), or other documentation sources?
|
- [ ] **Documentation:** If necessary, update relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs) like environment variables, the tutorials, or other documentation sources.
|
||||||
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
|
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
|
||||||
- [ ] **Testing:** Have you written and run sufficient tests to validate the changes?
|
- [ ] **Testing:** Perform manual tests to verify the implemented fix/feature works as intended AND does not break any other functionality. Take this as an opportunity to make screenshots of the feature/fix and include it in the PR description.
|
||||||
|
- [ ] **Agentic AI Code:**: Confirm this Pull Request is **not written by any AI Agent** or has at least gone through additional human review **and** manual testing. If any AI Agent is the co-author of this PR, it may lead to immediate closure of the PR.
|
||||||
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
|
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
|
||||||
- [ ] **Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
|
- [ ] **Title Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
|
||||||
- **BREAKING CHANGE**: Significant changes that may affect compatibility
|
- **BREAKING CHANGE**: Significant changes that may affect compatibility
|
||||||
- **build**: Changes that affect the build system or external dependencies
|
- **build**: Changes that affect the build system or external dependencies
|
||||||
- **ci**: Changes to our continuous integration processes or workflows
|
- **ci**: Changes to our continuous integration processes or workflows
|
||||||
|
|
|
||||||
69
CHANGELOG.md
69
CHANGELOG.md
|
|
@ -5,6 +5,75 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.6.33] - 2025-10-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 🎨 Workspace interface received a comprehensive redesign across Models, Knowledge, Prompts, and Tools sections, featuring reorganized controls, view filters for created vs shared items, tag selectors, improved visual hierarchy, and streamlined import/export functionality. [Commit](https://github.com/open-webui/open-webui/commit/2c59a288603d8c5f004f223ee00fef37cc763a8e), [Commit](https://github.com/open-webui/open-webui/commit/6050c86ab6ef6b8c96dd3f99c62a6867011b67a4), [Commit](https://github.com/open-webui/open-webui/commit/96ecb47bc71c072aa34ef2be10781b042bef4e8c), [Commit](https://github.com/open-webui/open-webui/commit/2250d102b28075a9611696e911536547abb8b38a), [Commit](https://github.com/open-webui/open-webui/commit/23c8f6d507bfee75ab0015a3e2972d5c26f7e9bf), [Commit](https://github.com/open-webui/open-webui/commit/a743b16728c6ae24b8befbc2d7f24eb9e20c4ad5)
|
||||||
|
- 🛠️ Functions admin interface received a comprehensive redesign with creator attribution display, ownership filters for created vs shared items, improved organization, and refined styling. [Commit](https://github.com/open-webui/open-webui/commit/f5e1a42f51acc0b9d5b63a33c1ca2e42470239c1)
|
||||||
|
- ⚡ Page initialization performance is significantly improved through parallel data loading and optimized folder API calls, reducing initial page load time. [#17559](https://github.com/open-webui/open-webui/pull/17559), [#17889](https://github.com/open-webui/open-webui/pull/17889)
|
||||||
|
- ⚡ Chat overview component is now dynamically loaded on demand, reducing initial page bundle size by approximately 470KB and improving first-screen loading speed. [#17595](https://github.com/open-webui/open-webui/pull/17595)
|
||||||
|
- 📁 Folders can now be attached to chats using the "#" command, automatically expanding to include all files within the folder for streamlined knowledge base integration. [Commit](https://github.com/open-webui/open-webui/commit/d2cb78179d66dc85188172a08622d4c97a2ea1ee)
|
||||||
|
- 📱 Progressive Web App now supports Android share target functionality, allowing users to share web pages, YouTube videos, and text directly to Open WebUI from the system share menu. [#17633](https://github.com/open-webui/open-webui/pull/17633), [#17125](https://github.com/open-webui/open-webui/issues/17125)
|
||||||
|
- 🗄️ Redis session storage is now available as an experimental option for OAuth authentication flows via the ENABLE_STAR_SESSIONS_MIDDLEWARE environment variable, providing shared session state across multi-replica deployments to address CSRF errors, though currently only basic Redis setups are supported. [#17223](https://github.com/open-webui/open-webui/pull/17223), [#15373](https://github.com/open-webui/open-webui/issues/15373), [Docs:Commit](https://github.com/open-webui/docs/commit/14052347f165d1b597615370373d7289ce44c7f9)
|
||||||
|
- 📊 Vega and Vega-Lite chart visualization renderers are now supported in code blocks, enabling inline rendering of data visualizations with automatic compilation of Vega-Lite specifications. [#18033](https://github.com/open-webui/open-webui/pull/18033), [#18040](https://github.com/open-webui/open-webui/pull/18040), [#18022](https://github.com/open-webui/open-webui/issues/18022)
|
||||||
|
- 🔗 OpenAI connections now support custom HTTP headers, enabling users to configure authentication and routing headers for specific deployment requirements. [#18021](https://github.com/open-webui/open-webui/pull/18021), [#9732](https://github.com/open-webui/open-webui/discussions/9732)
|
||||||
|
- 🔐 OpenID Connect authentication now supports OIDC providers without email scope via the ENABLE_OAUTH_WITHOUT_EMAIL environment variable, enabling compatibility with identity providers that don't expose email addresses. [#18047](https://github.com/open-webui/open-webui/pull/18047), [#18045](https://github.com/open-webui/open-webui/issues/18045)
|
||||||
|
- 🤖 Ollama model management modal now features individual model update cancellation, comprehensive tooltips for all buttons, and streamlined notification behavior to reduce toast spam. [#16863](https://github.com/open-webui/open-webui/pull/16863)
|
||||||
|
- ☁️ OneDrive file picker now includes search functionality and "My Organization" pivot for business accounts, enabling easier file discovery across organizational content. [#17930](https://github.com/open-webui/open-webui/pull/17930), [#17929](https://github.com/open-webui/open-webui/issues/17929)
|
||||||
|
- 📊 Chat overview flow diagram now supports toggling between vertical and horizontal layout orientations for improved visualization flexibility. [#17941](https://github.com/open-webui/open-webui/pull/17941)
|
||||||
|
- 🔊 OpenAI Text-to-Speech engine now supports additional parameters, allowing users to customize TTS behavior with provider-specific options via JSON configuration. [#17985](https://github.com/open-webui/open-webui/issues/17985), [#17188](https://github.com/open-webui/open-webui/pull/17188)
|
||||||
|
- 🛠️ Tool server list now displays server name, URL, and type (OpenAPI or MCP) for easier identification and management. [#18062](https://github.com/open-webui/open-webui/issues/18062)
|
||||||
|
- 📁 Folders now remember the last selected model, automatically applying it when starting new chats within that folder. [#17836](https://github.com/open-webui/open-webui/issues/17836)
|
||||||
|
- 🔢 Ollama embedding endpoint now supports the optional dimensions parameter for controlling embedding output size, compatible with Ollama v0.11.11 and later. [#17942](https://github.com/open-webui/open-webui/pull/17942)
|
||||||
|
- ⚡ Workspace knowledge page load time is improved by removing redundant API calls, enhancing overall responsiveness. [#18057](https://github.com/open-webui/open-webui/pull/18057)
|
||||||
|
- ⚡ File metadata query performance is enhanced by selecting only relevant columns instead of retrieving entire records, reducing database overhead. [#18013](https://github.com/open-webui/open-webui/pull/18013)
|
||||||
|
- 📄 Note PDF exports now include titles and properly render in dark mode with appropriate background colors. [Commit](https://github.com/open-webui/open-webui/commit/216fb5c3db1a223ffe6e72d97aa9551fe0e2d028)
|
||||||
|
- 📄 Docling document extraction now supports additional parameters for VLM pipeline configuration, enabling customized vision model settings. [#17363](https://github.com/open-webui/open-webui/pull/17363)
|
||||||
|
- ⚙️ Server startup script now supports passing arbitrary arguments to uvicorn, enabling custom server configuration options. [#17919](https://github.com/open-webui/open-webui/pull/17919), [#17918](https://github.com/open-webui/open-webui/issues/17918)
|
||||||
|
- 🔄 Various improvements were implemented across the frontend and backend to enhance performance, stability, and security.
|
||||||
|
- 🌐 Translations for German, Danish, Spanish, Korean, Portuguese (Brazil), Simplified Chinese, and Traditional Chinese were enhanced and expanded.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 💬 System prompts are no longer duplicated in chat requests, eliminating confusion and excessive token usage caused by repeated instructions being sent to models. [#17198](https://github.com/open-webui/open-webui/issues/17198), [#16855](https://github.com/open-webui/open-webui/issues/16855)
|
||||||
|
- 🔐 MCP OAuth 2.1 authentication now complies with the standard by implementing PKCE with S256 code challenge method and explicitly passing client credentials during token authorization, resolving "code_challenge: Field required" and "client_id: Field required" errors when connecting to OAuth-secured MCP servers. [Commit](https://github.com/open-webui/open-webui/commit/911a114ad459f5deebd97543c13c2b90196efb54), [#18010](https://github.com/open-webui/open-webui/issues/18010), [#18087](https://github.com/open-webui/open-webui/pull/18087)
|
||||||
|
- 🔐 OAuth signup flow now handles password hashing correctly by migrating from passlib to native bcrypt, preventing failures when passwords exceed 72 bytes. [#17917](https://github.com/open-webui/open-webui/issues/17917)
|
||||||
|
- 🔐 OAuth token refresh errors are resolved by properly registering and storing OAuth clients, fixing "Constructor parameter should be str" exceptions for Google, Microsoft, and OIDC providers. [#17829](https://github.com/open-webui/open-webui/issues/17829)
|
||||||
|
- 🔐 OAuth server metadata URL is now correctly accessed via the proper attribute, fixing automatic token refresh and logout functionality for Microsoft OAuth provider when OPENID_PROVIDER_URL is not set. [#18065](https://github.com/open-webui/open-webui/pull/18065)
|
||||||
|
- 🔐 OAuth credential decryption failures now allow the application to start gracefully with clear error messages instead of crashing, preventing complete service outages when WEBUI_SECRET_KEY mismatches occur during database migrations or environment changes. [#18094](https://github.com/open-webui/open-webui/pull/18094), [#18092](https://github.com/open-webui/open-webui/issues/18092)
|
||||||
|
- 🔐 OAuth 2.1 server discovery now correctly attempts all configured discovery URLs in sequence instead of only trying the first URL. [#17906](https://github.com/open-webui/open-webui/pull/17906), [#17904](https://github.com/open-webui/open-webui/issues/17904), [#18026](https://github.com/open-webui/open-webui/pull/18026)
|
||||||
|
- 🔐 Login redirect now correctly honors the redirect query parameter after authentication, ensuring users are returned to their intended destination with query parameters intact instead of defaulting to the homepage. [#18071](https://github.com/open-webui/open-webui/issues/18071)
|
||||||
|
- ☁️ OneDrive Business integration authentication regression is resolved, ensuring the popup now properly triggers when connecting to OneDrive accounts. [#17902](https://github.com/open-webui/open-webui/pull/17902), [#17825](https://github.com/open-webui/open-webui/discussions/17825), [#17816](https://github.com/open-webui/open-webui/issues/17816)
|
||||||
|
- 👥 Default group settings now persist correctly after page navigation, ensuring configuration changes are properly saved and retained. [#17899](https://github.com/open-webui/open-webui/issues/17899), [#18003](https://github.com/open-webui/open-webui/issues/18003)
|
||||||
|
- 📁 Folder data integrity is now verified on retrieval, automatically fixing orphaned folders with invalid parent references and ensuring proper cascading deletion of nested folder structures. [Commit](https://github.com/open-webui/open-webui/commit/5448618dd5ea181b9635b77040cef60926a902ff)
|
||||||
|
- 🗄️ Redis Sentinel and Redis Cluster configurations with the experimental ENABLE_STAR_SESSIONS_MIDDLEWARE feature are now properly isolated by making the feature opt-in only, preventing ReadOnlyError failures when connecting to read replicas in multi-node Redis deployments. [#18073](https://github.com/open-webui/open-webui/issues/18073)
|
||||||
|
- 📊 Mermaid and Vega diagram rendering now displays error toast notifications when syntax errors are detected, helping users identify and fix diagram issues instead of silently failing. [#18068](https://github.com/open-webui/open-webui/pull/18068)
|
||||||
|
- 🤖 Reasoning models that return reasoning_content instead of content no longer cause NoneType errors during chat title generation, follow-up suggestions, and tag generation. [#18080](https://github.com/open-webui/open-webui/pull/18080)
|
||||||
|
- 📚 Citation rendering now correctly handles multiple source references in a single bracket, parsing formats like [1,2] and [1, 2] into separate clickable citation links. [#18120](https://github.com/open-webui/open-webui/pull/18120)
|
||||||
|
- 🔍 Web search now handles individual source failures gracefully, continuing to process remaining sources instead of failing entirely when a single URL is unreachable or returns an error. [Commit](https://github.com/open-webui/open-webui/commit/e000494e488090c5f66989a2b3f89d3eaeb7946b), [Commit](https://github.com/open-webui/open-webui/commit/53e98620bff38ab9280aee5165af0a704bdd99b9)
|
||||||
|
- 🔍 Hybrid search with reranking now handles empty result sets gracefully instead of crashing with ValueError when all results are filtered out due to relevance thresholds. [#18096](https://github.com/open-webui/open-webui/issues/18096)
|
||||||
|
- 🔍 Reranking models without defined padding tokens now work correctly by automatically falling back to eos_token_id as pad_token_id, fixing "Cannot handle batch sizes > 1" errors for models like Qwen3-Reranker. [#18108](https://github.com/open-webui/open-webui/pull/18108), [#16027](https://github.com/open-webui/open-webui/discussions/16027)
|
||||||
|
- 🔍 Model selector search now correctly returns results for non-admin users by dynamically updating the search index when the model list changes, fixing a race condition that caused empty search results. [#17996](https://github.com/open-webui/open-webui/pull/17996), [#17960](https://github.com/open-webui/open-webui/pull/17960)
|
||||||
|
- ⚡ Task model function calling performance is improved by excluding base64 image data from payloads, significantly reducing token count and memory usage when images are present in conversations. [#17897](https://github.com/open-webui/open-webui/pull/17897)
|
||||||
|
- 🤖 Text selection "Ask" action now correctly recognizes and uses local models configured via direct connections instead of only showing external provider models. [#17896](https://github.com/open-webui/open-webui/issues/17896)
|
||||||
|
- 🛑 Task cancellation API now returns accurate response status, correctly reporting successful cancellations instead of incorrectly indicating failures. [#17920](https://github.com/open-webui/open-webui/issues/17920)
|
||||||
|
- 💬 Follow-up query suggestions are now generated and displayed in temporary chats, matching the behavior of saved chats. [#14987](https://github.com/open-webui/open-webui/issues/14987)
|
||||||
|
- 🔊 Azure Text-to-Speech now properly escapes special characters like ampersands in SSML, preventing HTTP 400 errors and ensuring audio generation succeeds for all text content. [#17962](https://github.com/open-webui/open-webui/issues/17962)
|
||||||
|
- 🛠️ OpenAPI tool server calls with optional parameters now execute successfully even when no arguments are provided, removing the incorrect requirement for a request body. [#18036](https://github.com/open-webui/open-webui/issues/18036)
|
||||||
|
- 🛠️ MCP mode tool server connections no longer incorrectly validate the OpenAPI path field, allowing seamless switching between OpenAPI and MCP connection types. [#17989](https://github.com/open-webui/open-webui/pull/17989), [#17988](https://github.com/open-webui/open-webui/issues/17988)
|
||||||
|
- 🛠️ Third-party tool responses containing non-UTF8 or invalid byte sequences are now handled gracefully without causing request failures. [#17882](https://github.com/open-webui/open-webui/pull/17882)
|
||||||
|
- 🎨 Workspace filter dropdown now correctly renders model tags as strings instead of displaying individual characters, fixing broken filtering interface when models have multiple tags. [#18034](https://github.com/open-webui/open-webui/issues/18034)
|
||||||
|
- ⌨️ Ctrl+Enter keyboard shortcut now correctly sends messages in mobile and narrow browser views on Chrome instead of inserting newlines. [#17975](https://github.com/open-webui/open-webui/issues/17975)
|
||||||
|
- ⌨️ Tab characters are now preserved when pasting code or formatted text into the chat input box in plain text mode. [#17958](https://github.com/open-webui/open-webui/issues/17958)
|
||||||
|
- 📋 Text selection copying from the chat input box now correctly copies only the selected text instead of the entire textbox content. [#17911](https://github.com/open-webui/open-webui/issues/17911)
|
||||||
|
- 🔍 Web search query logging now uses debug level instead of info level, preventing user search queries from appearing in production logs. [#17888](https://github.com/open-webui/open-webui/pull/17888)
|
||||||
|
- 📝 Debug print statements in middleware were removed to prevent excessive log pollution and respect configured logging levels. [#17943](https://github.com/open-webui/open-webui/issues/17943)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 🗄️ Milvus vector database dependency is updated from pymilvus 2.5.0 to 2.6.2, ensuring compatibility with newer Milvus versions but requiring users on older Milvus instances to either upgrade their database or manually downgrade the pymilvus package. [#18066](https://github.com/open-webui/open-webui/pull/18066)
|
||||||
|
|
||||||
## [0.6.32] - 2025-09-29
|
## [0.6.32] - 2025-09-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -605,8 +605,8 @@ def load_oauth_providers():
|
||||||
OAUTH_PROVIDERS.clear()
|
OAUTH_PROVIDERS.clear()
|
||||||
if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value:
|
if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value:
|
||||||
|
|
||||||
def google_oauth_register(client: OAuth):
|
def google_oauth_register(oauth: OAuth):
|
||||||
client.register(
|
client = oauth.register(
|
||||||
name="google",
|
name="google",
|
||||||
client_id=GOOGLE_CLIENT_ID.value,
|
client_id=GOOGLE_CLIENT_ID.value,
|
||||||
client_secret=GOOGLE_CLIENT_SECRET.value,
|
client_secret=GOOGLE_CLIENT_SECRET.value,
|
||||||
|
|
@ -621,6 +621,7 @@ def load_oauth_providers():
|
||||||
},
|
},
|
||||||
redirect_uri=GOOGLE_REDIRECT_URI.value,
|
redirect_uri=GOOGLE_REDIRECT_URI.value,
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
OAUTH_PROVIDERS["google"] = {
|
OAUTH_PROVIDERS["google"] = {
|
||||||
"redirect_uri": GOOGLE_REDIRECT_URI.value,
|
"redirect_uri": GOOGLE_REDIRECT_URI.value,
|
||||||
|
|
@ -633,8 +634,8 @@ def load_oauth_providers():
|
||||||
and MICROSOFT_CLIENT_TENANT_ID.value
|
and MICROSOFT_CLIENT_TENANT_ID.value
|
||||||
):
|
):
|
||||||
|
|
||||||
def microsoft_oauth_register(client: OAuth):
|
def microsoft_oauth_register(oauth: OAuth):
|
||||||
client.register(
|
client = oauth.register(
|
||||||
name="microsoft",
|
name="microsoft",
|
||||||
client_id=MICROSOFT_CLIENT_ID.value,
|
client_id=MICROSOFT_CLIENT_ID.value,
|
||||||
client_secret=MICROSOFT_CLIENT_SECRET.value,
|
client_secret=MICROSOFT_CLIENT_SECRET.value,
|
||||||
|
|
@ -649,6 +650,7 @@ def load_oauth_providers():
|
||||||
},
|
},
|
||||||
redirect_uri=MICROSOFT_REDIRECT_URI.value,
|
redirect_uri=MICROSOFT_REDIRECT_URI.value,
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
OAUTH_PROVIDERS["microsoft"] = {
|
OAUTH_PROVIDERS["microsoft"] = {
|
||||||
"redirect_uri": MICROSOFT_REDIRECT_URI.value,
|
"redirect_uri": MICROSOFT_REDIRECT_URI.value,
|
||||||
|
|
@ -658,8 +660,8 @@ def load_oauth_providers():
|
||||||
|
|
||||||
if GITHUB_CLIENT_ID.value and GITHUB_CLIENT_SECRET.value:
|
if GITHUB_CLIENT_ID.value and GITHUB_CLIENT_SECRET.value:
|
||||||
|
|
||||||
def github_oauth_register(client: OAuth):
|
def github_oauth_register(oauth: OAuth):
|
||||||
client.register(
|
client = oauth.register(
|
||||||
name="github",
|
name="github",
|
||||||
client_id=GITHUB_CLIENT_ID.value,
|
client_id=GITHUB_CLIENT_ID.value,
|
||||||
client_secret=GITHUB_CLIENT_SECRET.value,
|
client_secret=GITHUB_CLIENT_SECRET.value,
|
||||||
|
|
@ -677,6 +679,7 @@ def load_oauth_providers():
|
||||||
},
|
},
|
||||||
redirect_uri=GITHUB_CLIENT_REDIRECT_URI.value,
|
redirect_uri=GITHUB_CLIENT_REDIRECT_URI.value,
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
OAUTH_PROVIDERS["github"] = {
|
OAUTH_PROVIDERS["github"] = {
|
||||||
"redirect_uri": GITHUB_CLIENT_REDIRECT_URI.value,
|
"redirect_uri": GITHUB_CLIENT_REDIRECT_URI.value,
|
||||||
|
|
@ -690,7 +693,7 @@ def load_oauth_providers():
|
||||||
and OPENID_PROVIDER_URL.value
|
and OPENID_PROVIDER_URL.value
|
||||||
):
|
):
|
||||||
|
|
||||||
def oidc_oauth_register(client: OAuth):
|
def oidc_oauth_register(oauth: OAuth):
|
||||||
client_kwargs = {
|
client_kwargs = {
|
||||||
"scope": OAUTH_SCOPES.value,
|
"scope": OAUTH_SCOPES.value,
|
||||||
**(
|
**(
|
||||||
|
|
@ -716,7 +719,7 @@ def load_oauth_providers():
|
||||||
% ("S256", OAUTH_CODE_CHALLENGE_METHOD.value)
|
% ("S256", OAUTH_CODE_CHALLENGE_METHOD.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
client.register(
|
client = oauth.register(
|
||||||
name="oidc",
|
name="oidc",
|
||||||
client_id=OAUTH_CLIENT_ID.value,
|
client_id=OAUTH_CLIENT_ID.value,
|
||||||
client_secret=OAUTH_CLIENT_SECRET.value,
|
client_secret=OAUTH_CLIENT_SECRET.value,
|
||||||
|
|
@ -724,6 +727,7 @@ def load_oauth_providers():
|
||||||
client_kwargs=client_kwargs,
|
client_kwargs=client_kwargs,
|
||||||
redirect_uri=OPENID_REDIRECT_URI.value,
|
redirect_uri=OPENID_REDIRECT_URI.value,
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
OAUTH_PROVIDERS["oidc"] = {
|
OAUTH_PROVIDERS["oidc"] = {
|
||||||
"name": OAUTH_PROVIDER_NAME.value,
|
"name": OAUTH_PROVIDER_NAME.value,
|
||||||
|
|
@ -733,8 +737,8 @@ def load_oauth_providers():
|
||||||
|
|
||||||
if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value:
|
if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value:
|
||||||
|
|
||||||
def feishu_oauth_register(client: OAuth):
|
def feishu_oauth_register(oauth: OAuth):
|
||||||
client.register(
|
client = oauth.register(
|
||||||
name="feishu",
|
name="feishu",
|
||||||
client_id=FEISHU_CLIENT_ID.value,
|
client_id=FEISHU_CLIENT_ID.value,
|
||||||
client_secret=FEISHU_CLIENT_SECRET.value,
|
client_secret=FEISHU_CLIENT_SECRET.value,
|
||||||
|
|
@ -752,6 +756,7 @@ def load_oauth_providers():
|
||||||
},
|
},
|
||||||
redirect_uri=FEISHU_REDIRECT_URI.value,
|
redirect_uri=FEISHU_REDIRECT_URI.value,
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
OAUTH_PROVIDERS["feishu"] = {
|
OAUTH_PROVIDERS["feishu"] = {
|
||||||
"register": feishu_oauth_register,
|
"register": feishu_oauth_register,
|
||||||
|
|
@ -2310,6 +2315,18 @@ DOCLING_SERVER_URL = PersistentConfig(
|
||||||
os.getenv("DOCLING_SERVER_URL", "http://docling:5001"),
|
os.getenv("DOCLING_SERVER_URL", "http://docling:5001"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
docling_params = os.getenv("DOCLING_PARAMS", "")
|
||||||
|
try:
|
||||||
|
docling_params = json.loads(docling_params)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
docling_params = {}
|
||||||
|
|
||||||
|
DOCLING_PARAMS = PersistentConfig(
|
||||||
|
"DOCLING_PARAMS",
|
||||||
|
"rag.docling_params",
|
||||||
|
docling_params,
|
||||||
|
)
|
||||||
|
|
||||||
DOCLING_DO_OCR = PersistentConfig(
|
DOCLING_DO_OCR = PersistentConfig(
|
||||||
"DOCLING_DO_OCR",
|
"DOCLING_DO_OCR",
|
||||||
"rag.docling_do_ocr",
|
"rag.docling_do_ocr",
|
||||||
|
|
@ -3361,6 +3378,19 @@ AUDIO_TTS_OPENAI_API_KEY = PersistentConfig(
|
||||||
os.getenv("AUDIO_TTS_OPENAI_API_KEY", OPENAI_API_KEY),
|
os.getenv("AUDIO_TTS_OPENAI_API_KEY", OPENAI_API_KEY),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
audio_tts_openai_params = os.getenv("AUDIO_TTS_OPENAI_PARAMS", "")
|
||||||
|
try:
|
||||||
|
audio_tts_openai_params = json.loads(audio_tts_openai_params)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
audio_tts_openai_params = {}
|
||||||
|
|
||||||
|
AUDIO_TTS_OPENAI_PARAMS = PersistentConfig(
|
||||||
|
"AUDIO_TTS_OPENAI_PARAMS",
|
||||||
|
"audio.tts.openai.params",
|
||||||
|
audio_tts_openai_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
AUDIO_TTS_API_KEY = PersistentConfig(
|
AUDIO_TTS_API_KEY = PersistentConfig(
|
||||||
"AUDIO_TTS_API_KEY",
|
"AUDIO_TTS_API_KEY",
|
||||||
"audio.tts.api_key",
|
"audio.tts.api_key",
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,11 @@ ENABLE_FORWARD_USER_INFO_HEADERS = (
|
||||||
os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
|
os.environ.get("ENABLE_FORWARD_USER_INFO_HEADERS", "False").lower() == "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Experimental feature, may be removed in future
|
||||||
|
ENABLE_STAR_SESSIONS_MIDDLEWARE = (
|
||||||
|
os.environ.get("ENABLE_STAR_SESSIONS_MIDDLEWARE", "False").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# WEBUI_BUILD_HASH
|
# WEBUI_BUILD_HASH
|
||||||
####################################
|
####################################
|
||||||
|
|
@ -468,7 +473,9 @@ ENABLE_COMPRESSION_MIDDLEWARE = (
|
||||||
####################################
|
####################################
|
||||||
# OAUTH Configuration
|
# OAUTH Configuration
|
||||||
####################################
|
####################################
|
||||||
|
ENABLE_OAUTH_EMAIL_FALLBACK = (
|
||||||
|
os.environ.get("ENABLE_OAUTH_EMAIL_FALLBACK", "False").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
ENABLE_OAUTH_ID_TOKEN_COOKIE = (
|
ENABLE_OAUTH_ID_TOKEN_COOKIE = (
|
||||||
os.environ.get("ENABLE_OAUTH_ID_TOKEN_COOKIE", "True").lower() == "true"
|
os.environ.get("ENABLE_OAUTH_ID_TOKEN_COOKIE", "True").lower() == "true"
|
||||||
|
|
@ -482,7 +489,6 @@ OAUTH_SESSION_TOKEN_ENCRYPTION_KEY = os.environ.get(
|
||||||
"OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY
|
"OAUTH_SESSION_TOKEN_ENCRYPTION_KEY", WEBUI_SECRET_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# SCIM Configuration
|
# SCIM Configuration
|
||||||
####################################
|
####################################
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -175,13 +176,14 @@ from open_webui.config import (
|
||||||
AUDIO_STT_AZURE_LOCALES,
|
AUDIO_STT_AZURE_LOCALES,
|
||||||
AUDIO_STT_AZURE_BASE_URL,
|
AUDIO_STT_AZURE_BASE_URL,
|
||||||
AUDIO_STT_AZURE_MAX_SPEAKERS,
|
AUDIO_STT_AZURE_MAX_SPEAKERS,
|
||||||
AUDIO_TTS_API_KEY,
|
|
||||||
AUDIO_TTS_ENGINE,
|
AUDIO_TTS_ENGINE,
|
||||||
AUDIO_TTS_MODEL,
|
AUDIO_TTS_MODEL,
|
||||||
|
AUDIO_TTS_VOICE,
|
||||||
AUDIO_TTS_OPENAI_API_BASE_URL,
|
AUDIO_TTS_OPENAI_API_BASE_URL,
|
||||||
AUDIO_TTS_OPENAI_API_KEY,
|
AUDIO_TTS_OPENAI_API_KEY,
|
||||||
|
AUDIO_TTS_OPENAI_PARAMS,
|
||||||
|
AUDIO_TTS_API_KEY,
|
||||||
AUDIO_TTS_SPLIT_ON,
|
AUDIO_TTS_SPLIT_ON,
|
||||||
AUDIO_TTS_VOICE,
|
|
||||||
AUDIO_TTS_AZURE_SPEECH_REGION,
|
AUDIO_TTS_AZURE_SPEECH_REGION,
|
||||||
AUDIO_TTS_AZURE_SPEECH_BASE_URL,
|
AUDIO_TTS_AZURE_SPEECH_BASE_URL,
|
||||||
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
|
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
|
||||||
|
|
@ -247,6 +249,7 @@ from open_webui.config import (
|
||||||
EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
||||||
TIKA_SERVER_URL,
|
TIKA_SERVER_URL,
|
||||||
DOCLING_SERVER_URL,
|
DOCLING_SERVER_URL,
|
||||||
|
DOCLING_PARAMS,
|
||||||
DOCLING_DO_OCR,
|
DOCLING_DO_OCR,
|
||||||
DOCLING_FORCE_OCR,
|
DOCLING_FORCE_OCR,
|
||||||
DOCLING_OCR_ENGINE,
|
DOCLING_OCR_ENGINE,
|
||||||
|
|
@ -448,6 +451,7 @@ from open_webui.env import (
|
||||||
ENABLE_OTEL,
|
ENABLE_OTEL,
|
||||||
EXTERNAL_PWA_MANIFEST_URL,
|
EXTERNAL_PWA_MANIFEST_URL,
|
||||||
AIOHTTP_CLIENT_SESSION_SSL,
|
AIOHTTP_CLIENT_SESSION_SSL,
|
||||||
|
ENABLE_STAR_SESSIONS_MIDDLEWARE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -835,6 +839,7 @@ app.state.config.EXTERNAL_DOCUMENT_LOADER_URL = EXTERNAL_DOCUMENT_LOADER_URL
|
||||||
app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY = EXTERNAL_DOCUMENT_LOADER_API_KEY
|
app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY = EXTERNAL_DOCUMENT_LOADER_API_KEY
|
||||||
app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
|
app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
|
||||||
app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
|
app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
|
||||||
|
app.state.config.DOCLING_PARAMS = DOCLING_PARAMS
|
||||||
app.state.config.DOCLING_DO_OCR = DOCLING_DO_OCR
|
app.state.config.DOCLING_DO_OCR = DOCLING_DO_OCR
|
||||||
app.state.config.DOCLING_FORCE_OCR = DOCLING_FORCE_OCR
|
app.state.config.DOCLING_FORCE_OCR = DOCLING_FORCE_OCR
|
||||||
app.state.config.DOCLING_OCR_ENGINE = DOCLING_OCR_ENGINE
|
app.state.config.DOCLING_OCR_ENGINE = DOCLING_OCR_ENGINE
|
||||||
|
|
@ -1096,11 +1101,15 @@ app.state.config.AUDIO_STT_AZURE_LOCALES = AUDIO_STT_AZURE_LOCALES
|
||||||
app.state.config.AUDIO_STT_AZURE_BASE_URL = AUDIO_STT_AZURE_BASE_URL
|
app.state.config.AUDIO_STT_AZURE_BASE_URL = AUDIO_STT_AZURE_BASE_URL
|
||||||
app.state.config.AUDIO_STT_AZURE_MAX_SPEAKERS = AUDIO_STT_AZURE_MAX_SPEAKERS
|
app.state.config.AUDIO_STT_AZURE_MAX_SPEAKERS = AUDIO_STT_AZURE_MAX_SPEAKERS
|
||||||
|
|
||||||
app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
|
|
||||||
app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
|
|
||||||
app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
|
app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
|
||||||
|
|
||||||
app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
|
app.state.config.TTS_MODEL = AUDIO_TTS_MODEL
|
||||||
app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
|
app.state.config.TTS_VOICE = AUDIO_TTS_VOICE
|
||||||
|
|
||||||
|
app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
|
||||||
|
app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
|
||||||
|
app.state.config.TTS_OPENAI_PARAMS = AUDIO_TTS_OPENAI_PARAMS
|
||||||
|
|
||||||
app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
|
app.state.config.TTS_API_KEY = AUDIO_TTS_API_KEY
|
||||||
app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
|
app.state.config.TTS_SPLIT_ON = AUDIO_TTS_SPLIT_ON
|
||||||
|
|
||||||
|
|
@ -1171,12 +1180,32 @@ class RedirectMiddleware(BaseHTTPMiddleware):
|
||||||
path = request.url.path
|
path = request.url.path
|
||||||
query_params = dict(parse_qs(urlparse(str(request.url)).query))
|
query_params = dict(parse_qs(urlparse(str(request.url)).query))
|
||||||
|
|
||||||
|
redirect_params = {}
|
||||||
|
|
||||||
# Check for the specific watch path and the presence of 'v' parameter
|
# Check for the specific watch path and the presence of 'v' parameter
|
||||||
if path.endswith("/watch") and "v" in query_params:
|
if path.endswith("/watch") and "v" in query_params:
|
||||||
# Extract the first 'v' parameter
|
# Extract the first 'v' parameter
|
||||||
video_id = query_params["v"][0]
|
youtube_video_id = query_params["v"][0]
|
||||||
encoded_video_id = urlencode({"youtube": video_id})
|
redirect_params["youtube"] = youtube_video_id
|
||||||
redirect_url = f"/?{encoded_video_id}"
|
|
||||||
|
if "shared" in query_params and len(query_params["shared"]) > 0:
|
||||||
|
# PWA share_target support
|
||||||
|
|
||||||
|
text = query_params["shared"][0]
|
||||||
|
if text:
|
||||||
|
urls = re.match(r"https://\S+", text)
|
||||||
|
if urls:
|
||||||
|
from open_webui.retrieval.loaders.youtube import _parse_video_id
|
||||||
|
|
||||||
|
if youtube_video_id := _parse_video_id(urls[0]):
|
||||||
|
redirect_params["youtube"] = youtube_video_id
|
||||||
|
else:
|
||||||
|
redirect_params["load-url"] = urls[0]
|
||||||
|
else:
|
||||||
|
redirect_params["q"] = text
|
||||||
|
|
||||||
|
if redirect_params:
|
||||||
|
redirect_url = f"/?{urlencode(redirect_params)}"
|
||||||
return RedirectResponse(url=redirect_url)
|
return RedirectResponse(url=redirect_url)
|
||||||
|
|
||||||
# Proceed with the normal flow of other requests
|
# Proceed with the normal flow of other requests
|
||||||
|
|
@ -1476,7 +1505,7 @@ async def chat_completion(
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.get("chat_id") and (user and user.role != "admin"):
|
if metadata.get("chat_id") and (user and user.role != "admin"):
|
||||||
if metadata["chat_id"] != "local":
|
if not metadata["chat_id"].startswith("local:"):
|
||||||
chat = Chats.get_chat_by_id_and_user_id(metadata["chat_id"], user.id)
|
chat = Chats.get_chat_by_id_and_user_id(metadata["chat_id"], user.id)
|
||||||
if chat is None:
|
if chat is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -1503,6 +1532,7 @@ async def chat_completion(
|
||||||
response = await chat_completion_handler(request, form_data, user)
|
response = await chat_completion_handler(request, form_data, user)
|
||||||
if metadata.get("chat_id") and metadata.get("message_id"):
|
if metadata.get("chat_id") and metadata.get("message_id"):
|
||||||
try:
|
try:
|
||||||
|
if not metadata["chat_id"].startswith("local:"):
|
||||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||||
metadata["chat_id"],
|
metadata["chat_id"],
|
||||||
metadata["message_id"],
|
metadata["message_id"],
|
||||||
|
|
@ -1530,6 +1560,7 @@ async def chat_completion(
|
||||||
if metadata.get("chat_id") and metadata.get("message_id"):
|
if metadata.get("chat_id") and metadata.get("message_id"):
|
||||||
# Update the chat message with the error
|
# Update the chat message with the error
|
||||||
try:
|
try:
|
||||||
|
if not metadata["chat_id"].startswith("local:"):
|
||||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||||
metadata["chat_id"],
|
metadata["chat_id"],
|
||||||
metadata["message_id"],
|
metadata["message_id"],
|
||||||
|
|
@ -1905,13 +1936,20 @@ if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0:
|
||||||
"oauth_client_info", ""
|
"oauth_client_info", ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
oauth_client_info = decrypt_data(oauth_client_info)
|
oauth_client_info = decrypt_data(oauth_client_info)
|
||||||
app.state.oauth_client_manager.add_client(
|
app.state.oauth_client_manager.add_client(
|
||||||
f"mcp:{server_id}", OAuthClientInformationFull(**oauth_client_info)
|
f"mcp:{server_id}",
|
||||||
|
OAuthClientInformationFull(**oauth_client_info),
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(
|
||||||
|
f"Error adding OAuth client for MCP tool server {server_id}: {e}"
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if REDIS_URL:
|
if ENABLE_STAR_SESSIONS_MIDDLEWARE:
|
||||||
redis_session_store = RedisStore(
|
redis_session_store = RedisStore(
|
||||||
url=REDIS_URL,
|
url=REDIS_URL,
|
||||||
prefix=(f"{REDIS_KEY_PREFIX}:session:" if REDIS_KEY_PREFIX else "session:"),
|
prefix=(f"{REDIS_KEY_PREFIX}:session:" if REDIS_KEY_PREFIX else "session:"),
|
||||||
|
|
@ -2006,6 +2044,11 @@ async def get_manifest_json():
|
||||||
"purpose": "maskable",
|
"purpose": "maskable",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"share_target": {
|
||||||
|
"action": "/",
|
||||||
|
"method": "GET",
|
||||||
|
"params": {"text": "shared"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,9 @@ class FilesTable:
|
||||||
created_at=file.created_at,
|
created_at=file.created_at,
|
||||||
updated_at=file.updated_at,
|
updated_at=file.updated_at,
|
||||||
)
|
)
|
||||||
for file in db.query(File)
|
for file in db.query(
|
||||||
|
File.id, File.meta, File.created_at, File.updated_at
|
||||||
|
)
|
||||||
.filter(File.id.in_(ids))
|
.filter(File.id.in_(ids))
|
||||||
.order_by(File.updated_at.desc())
|
.order_by(File.updated_at.desc())
|
||||||
.all()
|
.all()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from open_webui.internal.db import Base, JSONField, get_db
|
from open_webui.internal.db import Base, JSONField, get_db
|
||||||
from open_webui.models.users import Users
|
from open_webui.models.users import Users, UserModel
|
||||||
from open_webui.env import SRC_LOG_LEVELS
|
from open_webui.env import SRC_LOG_LEVELS
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, Index
|
from sqlalchemy import BigInteger, Boolean, Column, String, Text, Index
|
||||||
|
|
@ -76,6 +76,10 @@ class FunctionWithValvesModel(BaseModel):
|
||||||
####################
|
####################
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionUserResponse(FunctionModel):
|
||||||
|
user: Optional[UserModel] = None
|
||||||
|
|
||||||
|
|
||||||
class FunctionResponse(BaseModel):
|
class FunctionResponse(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
user_id: str
|
user_id: str
|
||||||
|
|
@ -203,6 +207,28 @@ class FunctionsTable:
|
||||||
FunctionModel.model_validate(function) for function in functions
|
FunctionModel.model_validate(function) for function in functions
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def get_function_list(self) -> list[FunctionUserResponse]:
|
||||||
|
with get_db() as db:
|
||||||
|
functions = db.query(Function).order_by(Function.updated_at.desc()).all()
|
||||||
|
user_ids = list(set(func.user_id for func in functions))
|
||||||
|
|
||||||
|
users = Users.get_users_by_user_ids(user_ids) if user_ids else []
|
||||||
|
users_dict = {user.id: user for user in users}
|
||||||
|
|
||||||
|
return [
|
||||||
|
FunctionUserResponse.model_validate(
|
||||||
|
{
|
||||||
|
**FunctionModel.model_validate(func).model_dump(),
|
||||||
|
"user": (
|
||||||
|
users_dict.get(func.user_id).model_dump()
|
||||||
|
if func.user_id in users_dict
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for func in functions
|
||||||
|
]
|
||||||
|
|
||||||
def get_functions_by_type(
|
def get_functions_by_type(
|
||||||
self, type: str, active_only=False
|
self, type: str, active_only=False
|
||||||
) -> list[FunctionModel]:
|
) -> list[FunctionModel]:
|
||||||
|
|
|
||||||
|
|
@ -346,11 +346,9 @@ class Loader:
|
||||||
self.engine == "document_intelligence"
|
self.engine == "document_intelligence"
|
||||||
and self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT") != ""
|
and self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT") != ""
|
||||||
and (
|
and (
|
||||||
file_ext in ["pdf", "xls", "xlsx", "docx", "ppt", "pptx"]
|
file_ext in ["pdf", "docx", "ppt", "pptx"]
|
||||||
or file_content_type
|
or file_content_type
|
||||||
in [
|
in [
|
||||||
"application/vnd.ms-excel",
|
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"application/vnd.ms-powerpoint",
|
"application/vnd.ms-powerpoint",
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
|
|
||||||
|
|
@ -157,3 +157,10 @@ class YoutubeLoader:
|
||||||
f"No transcript found for any of the specified languages: {languages_tried}. Verify if the video has transcripts, add more languages if needed."
|
f"No transcript found for any of the specified languages: {languages_tried}. Verify if the video has transcripts, add more languages if needed."
|
||||||
)
|
)
|
||||||
raise NoTranscriptFound(self.video_id, self.language, list(transcript_list))
|
raise NoTranscriptFound(self.video_id, self.language, list(transcript_list))
|
||||||
|
|
||||||
|
async def aload(self) -> Generator[Document, None, None]:
|
||||||
|
"""Asynchronously load YouTube transcripts into `Document` objects."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(None, self.load)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import requests
|
||||||
import hashlib
|
import hashlib
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from huggingface_hub import snapshot_download
|
from huggingface_hub import snapshot_download
|
||||||
|
|
@ -16,6 +17,7 @@ from langchain_core.documents import Document
|
||||||
from open_webui.config import VECTOR_DB
|
from open_webui.config import VECTOR_DB
|
||||||
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
||||||
|
|
||||||
|
|
||||||
from open_webui.models.users import UserModel
|
from open_webui.models.users import UserModel
|
||||||
from open_webui.models.files import Files
|
from open_webui.models.files import Files
|
||||||
from open_webui.models.knowledge import Knowledges
|
from open_webui.models.knowledge import Knowledges
|
||||||
|
|
@ -27,6 +29,9 @@ from open_webui.retrieval.vector.main import GetResult
|
||||||
from open_webui.utils.access_control import has_access
|
from open_webui.utils.access_control import has_access
|
||||||
from open_webui.utils.misc import get_message_list
|
from open_webui.utils.misc import get_message_list
|
||||||
|
|
||||||
|
from open_webui.retrieval.web.utils import get_web_loader
|
||||||
|
from open_webui.retrieval.loaders.youtube import YoutubeLoader
|
||||||
|
|
||||||
|
|
||||||
from open_webui.env import (
|
from open_webui.env import (
|
||||||
SRC_LOG_LEVELS,
|
SRC_LOG_LEVELS,
|
||||||
|
|
@ -49,6 +54,33 @@ from langchain_core.callbacks import CallbackManagerForRetrieverRun
|
||||||
from langchain_core.retrievers import BaseRetriever
|
from langchain_core.retrievers import BaseRetriever
|
||||||
|
|
||||||
|
|
||||||
|
def is_youtube_url(url: str) -> bool:
|
||||||
|
youtube_regex = r"^(https?://)?(www\.)?(youtube\.com|youtu\.be)/.+$"
|
||||||
|
return re.match(youtube_regex, url) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def get_loader(request, url: str):
|
||||||
|
if is_youtube_url(url):
|
||||||
|
return YoutubeLoader(
|
||||||
|
url,
|
||||||
|
language=request.app.state.config.YOUTUBE_LOADER_LANGUAGE,
|
||||||
|
proxy_url=request.app.state.config.YOUTUBE_LOADER_PROXY_URL,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return get_web_loader(
|
||||||
|
url,
|
||||||
|
verify_ssl=request.app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION,
|
||||||
|
requests_per_second=request.app.state.config.WEB_LOADER_CONCURRENT_REQUESTS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_content_from_url(request, url: str) -> str:
|
||||||
|
loader = get_loader(request, url)
|
||||||
|
docs = loader.load()
|
||||||
|
content = " ".join([doc.page_content for doc in docs])
|
||||||
|
return content, docs
|
||||||
|
|
||||||
|
|
||||||
class VectorSearchRetriever(BaseRetriever):
|
class VectorSearchRetriever(BaseRetriever):
|
||||||
collection_name: Any
|
collection_name: Any
|
||||||
embedding_function: Any
|
embedding_function: Any
|
||||||
|
|
@ -188,7 +220,11 @@ def query_doc_with_hybrid_search(
|
||||||
zip(distances, metadatas, documents), key=lambda x: x[0], reverse=True
|
zip(distances, metadatas, documents), key=lambda x: x[0], reverse=True
|
||||||
)
|
)
|
||||||
sorted_items = sorted_items[:k]
|
sorted_items = sorted_items[:k]
|
||||||
|
|
||||||
|
if sorted_items:
|
||||||
distances, documents, metadatas = map(list, zip(*sorted_items))
|
distances, documents, metadatas = map(list, zip(*sorted_items))
|
||||||
|
else:
|
||||||
|
distances, documents, metadatas = [], [], []
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"distances": [distances],
|
"distances": [distances],
|
||||||
|
|
@ -571,6 +607,13 @@ def get_sources_from_items(
|
||||||
"metadatas": [[{"file_id": chat.id, "name": chat.title}]],
|
"metadatas": [[{"file_id": chat.id, "name": chat.title}]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elif item.get("type") == "url":
|
||||||
|
content, docs = get_content_from_url(request, item.get("url"))
|
||||||
|
if docs:
|
||||||
|
query_result = {
|
||||||
|
"documents": [[content]],
|
||||||
|
"metadatas": [[{"url": item.get("url"), "name": item.get("url")}]],
|
||||||
|
}
|
||||||
elif item.get("type") == "file":
|
elif item.get("type") == "file":
|
||||||
if (
|
if (
|
||||||
item.get("context") == "full"
|
item.get("context") == "full"
|
||||||
|
|
@ -736,7 +779,6 @@ def get_sources_from_items(
|
||||||
sources.append(source)
|
sources.append(source)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
|
|
||||||
return sources
|
return sources
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ def safe_validate_urls(url: Sequence[str]) -> Sequence[str]:
|
||||||
try:
|
try:
|
||||||
if validate_url(u):
|
if validate_url(u):
|
||||||
valid_urls.append(u)
|
valid_urls.append(u)
|
||||||
except ValueError:
|
except Exception as e:
|
||||||
|
log.debug(f"Invalid URL {u}: {str(e)}")
|
||||||
continue
|
continue
|
||||||
return valid_urls
|
return valid_urls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
import html
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pydub import AudioSegment
|
from pydub import AudioSegment
|
||||||
from pydub.silence import split_on_silence
|
from pydub.silence import split_on_silence
|
||||||
|
|
@ -153,6 +154,7 @@ def set_faster_whisper_model(model: str, auto_update: bool = False):
|
||||||
class TTSConfigForm(BaseModel):
|
class TTSConfigForm(BaseModel):
|
||||||
OPENAI_API_BASE_URL: str
|
OPENAI_API_BASE_URL: str
|
||||||
OPENAI_API_KEY: str
|
OPENAI_API_KEY: str
|
||||||
|
OPENAI_PARAMS: Optional[dict] = None
|
||||||
API_KEY: str
|
API_KEY: str
|
||||||
ENGINE: str
|
ENGINE: str
|
||||||
MODEL: str
|
MODEL: str
|
||||||
|
|
@ -189,6 +191,7 @@ async def get_audio_config(request: Request, user=Depends(get_admin_user)):
|
||||||
"tts": {
|
"tts": {
|
||||||
"OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
|
"OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
|
||||||
"OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
|
"OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
|
||||||
|
"OPENAI_PARAMS": request.app.state.config.TTS_OPENAI_PARAMS,
|
||||||
"API_KEY": request.app.state.config.TTS_API_KEY,
|
"API_KEY": request.app.state.config.TTS_API_KEY,
|
||||||
"ENGINE": request.app.state.config.TTS_ENGINE,
|
"ENGINE": request.app.state.config.TTS_ENGINE,
|
||||||
"MODEL": request.app.state.config.TTS_MODEL,
|
"MODEL": request.app.state.config.TTS_MODEL,
|
||||||
|
|
@ -221,6 +224,7 @@ async def update_audio_config(
|
||||||
):
|
):
|
||||||
request.app.state.config.TTS_OPENAI_API_BASE_URL = form_data.tts.OPENAI_API_BASE_URL
|
request.app.state.config.TTS_OPENAI_API_BASE_URL = form_data.tts.OPENAI_API_BASE_URL
|
||||||
request.app.state.config.TTS_OPENAI_API_KEY = form_data.tts.OPENAI_API_KEY
|
request.app.state.config.TTS_OPENAI_API_KEY = form_data.tts.OPENAI_API_KEY
|
||||||
|
request.app.state.config.TTS_OPENAI_PARAMS = form_data.tts.OPENAI_PARAMS
|
||||||
request.app.state.config.TTS_API_KEY = form_data.tts.API_KEY
|
request.app.state.config.TTS_API_KEY = form_data.tts.API_KEY
|
||||||
request.app.state.config.TTS_ENGINE = form_data.tts.ENGINE
|
request.app.state.config.TTS_ENGINE = form_data.tts.ENGINE
|
||||||
request.app.state.config.TTS_MODEL = form_data.tts.MODEL
|
request.app.state.config.TTS_MODEL = form_data.tts.MODEL
|
||||||
|
|
@ -261,12 +265,13 @@ async def update_audio_config(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"tts": {
|
"tts": {
|
||||||
"OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
|
|
||||||
"OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
|
|
||||||
"API_KEY": request.app.state.config.TTS_API_KEY,
|
|
||||||
"ENGINE": request.app.state.config.TTS_ENGINE,
|
"ENGINE": request.app.state.config.TTS_ENGINE,
|
||||||
"MODEL": request.app.state.config.TTS_MODEL,
|
"MODEL": request.app.state.config.TTS_MODEL,
|
||||||
"VOICE": request.app.state.config.TTS_VOICE,
|
"VOICE": request.app.state.config.TTS_VOICE,
|
||||||
|
"OPENAI_API_BASE_URL": request.app.state.config.TTS_OPENAI_API_BASE_URL,
|
||||||
|
"OPENAI_API_KEY": request.app.state.config.TTS_OPENAI_API_KEY,
|
||||||
|
"OPENAI_PARAMS": request.app.state.config.TTS_OPENAI_PARAMS,
|
||||||
|
"API_KEY": request.app.state.config.TTS_API_KEY,
|
||||||
"SPLIT_ON": request.app.state.config.TTS_SPLIT_ON,
|
"SPLIT_ON": request.app.state.config.TTS_SPLIT_ON,
|
||||||
"AZURE_SPEECH_REGION": request.app.state.config.TTS_AZURE_SPEECH_REGION,
|
"AZURE_SPEECH_REGION": request.app.state.config.TTS_AZURE_SPEECH_REGION,
|
||||||
"AZURE_SPEECH_BASE_URL": request.app.state.config.TTS_AZURE_SPEECH_BASE_URL,
|
"AZURE_SPEECH_BASE_URL": request.app.state.config.TTS_AZURE_SPEECH_BASE_URL,
|
||||||
|
|
@ -336,6 +341,11 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
||||||
async with aiohttp.ClientSession(
|
async with aiohttp.ClientSession(
|
||||||
timeout=timeout, trust_env=True
|
timeout=timeout, trust_env=True
|
||||||
) as session:
|
) as session:
|
||||||
|
payload = {
|
||||||
|
**payload,
|
||||||
|
**(request.app.state.config.TTS_OPENAI_PARAMS or {}),
|
||||||
|
}
|
||||||
|
|
||||||
r = await session.post(
|
r = await session.post(
|
||||||
url=f"{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
|
url=f"{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
|
||||||
json=payload,
|
json=payload,
|
||||||
|
|
@ -458,7 +468,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
|
data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
|
||||||
<voice name="{language}">{payload["input"]}</voice>
|
<voice name="{language}">{html.escape(payload["input"])}</voice>
|
||||||
</speak>"""
|
</speak>"""
|
||||||
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||||
async with aiohttp.ClientSession(
|
async with aiohttp.ClientSession(
|
||||||
|
|
|
||||||
|
|
@ -340,11 +340,12 @@ async def model_response_handler(request, channel, message, user):
|
||||||
if file.get("type", "") == "image":
|
if file.get("type", "") == "image":
|
||||||
images.append(file.get("url", ""))
|
images.append(file.get("url", ""))
|
||||||
|
|
||||||
|
thread_history_string = "\n\n".join(thread_history)
|
||||||
system_message = {
|
system_message = {
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": f"You are {model.get('name', model_id)}, participating in a threaded conversation. Be concise and conversational."
|
"content": f"You are {model.get('name', model_id)}, participating in a threaded conversation. Be concise and conversational."
|
||||||
+ (
|
+ (
|
||||||
f"Here's the thread history:\n\n{''.join([f'{msg}' for msg in thread_history])}\n\nContinue the conversation naturally as {model.get('name', model_id)}, addressing the most recent message while being aware of the full context."
|
f"Here's the thread history:\n\n\n{thread_history_string}\n\n\nContinue the conversation naturally as {model.get('name', model_id)}, addressing the most recent message while being aware of the full context."
|
||||||
if thread_history
|
if thread_history
|
||||||
else ""
|
else ""
|
||||||
),
|
),
|
||||||
|
|
@ -384,6 +385,7 @@ async def model_response_handler(request, channel, message, user):
|
||||||
)
|
)
|
||||||
|
|
||||||
if res:
|
if res:
|
||||||
|
if res.get("choices", []) and len(res["choices"]) > 0:
|
||||||
await update_message_by_id(
|
await update_message_by_id(
|
||||||
channel.id,
|
channel.id,
|
||||||
response_message.id,
|
response_message.id,
|
||||||
|
|
@ -397,6 +399,20 @@ async def model_response_handler(request, channel, message, user):
|
||||||
),
|
),
|
||||||
user,
|
user,
|
||||||
)
|
)
|
||||||
|
elif res.get("error", None):
|
||||||
|
await update_message_by_id(
|
||||||
|
channel.id,
|
||||||
|
response_message.id,
|
||||||
|
MessageForm(
|
||||||
|
**{
|
||||||
|
"content": f"Error: {res['error']}",
|
||||||
|
"meta": {
|
||||||
|
"done": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
user,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.info(e)
|
log.info(e)
|
||||||
pass
|
pass
|
||||||
|
|
@ -436,7 +452,7 @@ async def new_message_handler(
|
||||||
}
|
}
|
||||||
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
event_data,
|
event_data,
|
||||||
to=f"channel:{channel.id}",
|
to=f"channel:{channel.id}",
|
||||||
)
|
)
|
||||||
|
|
@ -447,7 +463,7 @@ async def new_message_handler(
|
||||||
|
|
||||||
if parent_message:
|
if parent_message:
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": parent_message.id,
|
"message_id": parent_message.id,
|
||||||
|
|
@ -644,7 +660,7 @@ async def update_message_by_id(
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": message.id,
|
"message_id": message.id,
|
||||||
|
|
@ -708,7 +724,7 @@ async def add_reaction_to_message(
|
||||||
message = Messages.get_message_by_id(message_id)
|
message = Messages.get_message_by_id(message_id)
|
||||||
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": message.id,
|
"message_id": message.id,
|
||||||
|
|
@ -774,7 +790,7 @@ async def remove_reaction_by_id_and_user_id_and_name(
|
||||||
message = Messages.get_message_by_id(message_id)
|
message = Messages.get_message_by_id(message_id)
|
||||||
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": message.id,
|
"message_id": message.id,
|
||||||
|
|
@ -839,7 +855,7 @@ async def delete_message_by_id(
|
||||||
try:
|
try:
|
||||||
Messages.delete_message_by_id(message_id)
|
Messages.delete_message_by_id(message_id)
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": message.id,
|
"message_id": message.id,
|
||||||
|
|
@ -862,7 +878,7 @@ async def delete_message_by_id(
|
||||||
|
|
||||||
if parent_message:
|
if parent_message:
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": channel.id,
|
"channel_id": channel.id,
|
||||||
"message_id": parent_message.id,
|
"message_id": parent_message.id,
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ async def verify_tool_servers_config(
|
||||||
)
|
)
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
discovery_urls[0]
|
discovery_url
|
||||||
) as oauth_server_metadata_response:
|
) as oauth_server_metadata_response:
|
||||||
if oauth_server_metadata_response.status == 200:
|
if oauth_server_metadata_response.status == 200:
|
||||||
try:
|
try:
|
||||||
|
|
@ -234,7 +234,7 @@ async def verify_tool_servers_config(
|
||||||
)
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=f"Failed to parse OAuth 2.1 discovery document from {discovery_urls[0]}",
|
detail=f"Failed to parse OAuth 2.1 discovery document from {discovery_url}",
|
||||||
)
|
)
|
||||||
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,15 @@ async def get_folders(user=Depends(get_verified_user)):
|
||||||
folders = Folders.get_folders_by_user_id(user.id)
|
folders = Folders.get_folders_by_user_id(user.id)
|
||||||
|
|
||||||
# Verify folder data integrity
|
# Verify folder data integrity
|
||||||
|
folder_list = []
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
|
if folder.parent_id and not Folders.get_folder_by_id_and_user_id(
|
||||||
|
folder.parent_id, user.id
|
||||||
|
):
|
||||||
|
folder = Folders.update_folder_parent_id_by_id_and_user_id(
|
||||||
|
folder.id, user.id, None
|
||||||
|
)
|
||||||
|
|
||||||
if folder.data:
|
if folder.data:
|
||||||
if "files" in folder.data:
|
if "files" in folder.data:
|
||||||
valid_files = []
|
valid_files = []
|
||||||
|
|
@ -74,12 +82,9 @@ async def get_folders(user=Depends(get_verified_user)):
|
||||||
folder.id, user.id, FolderUpdateForm(data=folder.data)
|
folder.id, user.id, FolderUpdateForm(data=folder.data)
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
folder_list.append(FolderNameIdResponse(**folder.model_dump()))
|
||||||
{
|
|
||||||
**folder.model_dump(),
|
return folder_list
|
||||||
}
|
|
||||||
for folder in folders
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
|
|
@ -265,7 +270,10 @@ async def delete_folder_by_id(
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
)
|
)
|
||||||
|
|
||||||
folder = Folders.get_folder_by_id_and_user_id(id, user.id)
|
folders = []
|
||||||
|
folders.append(Folders.get_folder_by_id_and_user_id(id, user.id))
|
||||||
|
while folders:
|
||||||
|
folder = folders.pop()
|
||||||
if folder:
|
if folder:
|
||||||
try:
|
try:
|
||||||
folder_ids = Folders.delete_folder_by_id_and_user_id(id, user.id)
|
folder_ids = Folders.delete_folder_by_id_and_user_id(id, user.id)
|
||||||
|
|
@ -280,6 +288,13 @@ async def delete_folder_by_id(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.DEFAULT("Error deleting folder"),
|
detail=ERROR_MESSAGES.DEFAULT("Error deleting folder"),
|
||||||
)
|
)
|
||||||
|
finally:
|
||||||
|
# Get all subfolders
|
||||||
|
subfolders = Folders.get_folders_by_parent_id_and_user_id(
|
||||||
|
folder.id, user.id
|
||||||
|
)
|
||||||
|
folders.extend(subfolders)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from open_webui.models.functions import (
|
||||||
FunctionForm,
|
FunctionForm,
|
||||||
FunctionModel,
|
FunctionModel,
|
||||||
FunctionResponse,
|
FunctionResponse,
|
||||||
|
FunctionUserResponse,
|
||||||
FunctionWithValvesModel,
|
FunctionWithValvesModel,
|
||||||
Functions,
|
Functions,
|
||||||
)
|
)
|
||||||
|
|
@ -42,6 +43,11 @@ async def get_functions(user=Depends(get_verified_user)):
|
||||||
return Functions.get_functions()
|
return Functions.get_functions()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", response_model=list[FunctionUserResponse])
|
||||||
|
async def get_function_list(user=Depends(get_admin_user)):
|
||||||
|
return Functions.get_function_list()
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# ExportFunctions
|
# ExportFunctions
|
||||||
############################
|
############################
|
||||||
|
|
|
||||||
|
|
@ -1020,6 +1020,10 @@ class GenerateEmbedForm(BaseModel):
|
||||||
options: Optional[dict] = None
|
options: Optional[dict] = None
|
||||||
keep_alive: Optional[Union[int, str]] = None
|
keep_alive: Optional[Union[int, str]] = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(
|
||||||
|
extra="allow",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/embed")
|
@router.post("/api/embed")
|
||||||
@router.post("/api/embed/{url_idx}")
|
@router.post("/api/embed/{url_idx}")
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,9 @@ async def get_headers_and_cookies(
|
||||||
if token:
|
if token:
|
||||||
headers["Authorization"] = f"Bearer {token}"
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
|
||||||
|
if config.get("headers") and isinstance(config.get("headers"), dict):
|
||||||
|
headers = {**headers, **config.get("headers")}
|
||||||
|
|
||||||
return headers, cookies
|
return headers, cookies
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -70,6 +71,7 @@ from open_webui.retrieval.web.firecrawl import search_firecrawl
|
||||||
from open_webui.retrieval.web.external import search_external
|
from open_webui.retrieval.web.external import search_external
|
||||||
|
|
||||||
from open_webui.retrieval.utils import (
|
from open_webui.retrieval.utils import (
|
||||||
|
get_content_from_url,
|
||||||
get_embedding_function,
|
get_embedding_function,
|
||||||
get_reranking_function,
|
get_reranking_function,
|
||||||
get_model_path,
|
get_model_path,
|
||||||
|
|
@ -189,6 +191,26 @@ def get_rf(
|
||||||
log.error(f"CrossEncoder: {e}")
|
log.error(f"CrossEncoder: {e}")
|
||||||
raise Exception(ERROR_MESSAGES.DEFAULT("CrossEncoder error"))
|
raise Exception(ERROR_MESSAGES.DEFAULT("CrossEncoder error"))
|
||||||
|
|
||||||
|
# Safely adjust pad_token_id if missing as some models do not have this in config
|
||||||
|
try:
|
||||||
|
model_cfg = getattr(rf, "model", None)
|
||||||
|
if model_cfg and hasattr(model_cfg, "config"):
|
||||||
|
cfg = model_cfg.config
|
||||||
|
if getattr(cfg, "pad_token_id", None) is None:
|
||||||
|
# Fallback to eos_token_id when available
|
||||||
|
eos = getattr(cfg, "eos_token_id", None)
|
||||||
|
if eos is not None:
|
||||||
|
cfg.pad_token_id = eos
|
||||||
|
log.debug(
|
||||||
|
f"Missing pad_token_id detected; set to eos_token_id={eos}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.warning(
|
||||||
|
"Neither pad_token_id nor eos_token_id present in model config"
|
||||||
|
)
|
||||||
|
except Exception as e2:
|
||||||
|
log.warning(f"Failed to adjust pad_token_id on CrossEncoder: {e2}")
|
||||||
|
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -429,6 +451,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
||||||
"EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
"EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
||||||
"TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL,
|
"TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL,
|
||||||
"DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL,
|
"DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL,
|
||||||
|
"DOCLING_PARAMS": request.app.state.config.DOCLING_PARAMS,
|
||||||
"DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR,
|
"DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR,
|
||||||
"DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR,
|
"DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR,
|
||||||
"DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE,
|
"DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE,
|
||||||
|
|
@ -590,6 +613,7 @@ class ConfigForm(BaseModel):
|
||||||
# Content extraction settings
|
# Content extraction settings
|
||||||
CONTENT_EXTRACTION_ENGINE: Optional[str] = None
|
CONTENT_EXTRACTION_ENGINE: Optional[str] = None
|
||||||
PDF_EXTRACT_IMAGES: Optional[bool] = None
|
PDF_EXTRACT_IMAGES: Optional[bool] = None
|
||||||
|
|
||||||
DATALAB_MARKER_API_KEY: Optional[str] = None
|
DATALAB_MARKER_API_KEY: Optional[str] = None
|
||||||
DATALAB_MARKER_API_BASE_URL: Optional[str] = None
|
DATALAB_MARKER_API_BASE_URL: Optional[str] = None
|
||||||
DATALAB_MARKER_ADDITIONAL_CONFIG: Optional[str] = None
|
DATALAB_MARKER_ADDITIONAL_CONFIG: Optional[str] = None
|
||||||
|
|
@ -601,11 +625,13 @@ class ConfigForm(BaseModel):
|
||||||
DATALAB_MARKER_FORMAT_LINES: Optional[bool] = None
|
DATALAB_MARKER_FORMAT_LINES: Optional[bool] = None
|
||||||
DATALAB_MARKER_USE_LLM: Optional[bool] = None
|
DATALAB_MARKER_USE_LLM: Optional[bool] = None
|
||||||
DATALAB_MARKER_OUTPUT_FORMAT: Optional[str] = None
|
DATALAB_MARKER_OUTPUT_FORMAT: Optional[str] = None
|
||||||
|
|
||||||
EXTERNAL_DOCUMENT_LOADER_URL: Optional[str] = None
|
EXTERNAL_DOCUMENT_LOADER_URL: Optional[str] = None
|
||||||
EXTERNAL_DOCUMENT_LOADER_API_KEY: Optional[str] = None
|
EXTERNAL_DOCUMENT_LOADER_API_KEY: Optional[str] = None
|
||||||
|
|
||||||
TIKA_SERVER_URL: Optional[str] = None
|
TIKA_SERVER_URL: Optional[str] = None
|
||||||
DOCLING_SERVER_URL: Optional[str] = None
|
DOCLING_SERVER_URL: Optional[str] = None
|
||||||
|
DOCLING_PARAMS: Optional[dict] = None
|
||||||
DOCLING_DO_OCR: Optional[bool] = None
|
DOCLING_DO_OCR: Optional[bool] = None
|
||||||
DOCLING_FORCE_OCR: Optional[bool] = None
|
DOCLING_FORCE_OCR: Optional[bool] = None
|
||||||
DOCLING_OCR_ENGINE: Optional[str] = None
|
DOCLING_OCR_ENGINE: Optional[str] = None
|
||||||
|
|
@ -782,6 +808,11 @@ async def update_rag_config(
|
||||||
if form_data.DOCLING_SERVER_URL is not None
|
if form_data.DOCLING_SERVER_URL is not None
|
||||||
else request.app.state.config.DOCLING_SERVER_URL
|
else request.app.state.config.DOCLING_SERVER_URL
|
||||||
)
|
)
|
||||||
|
request.app.state.config.DOCLING_PARAMS = (
|
||||||
|
form_data.DOCLING_PARAMS
|
||||||
|
if form_data.DOCLING_PARAMS is not None
|
||||||
|
else request.app.state.config.DOCLING_PARAMS
|
||||||
|
)
|
||||||
request.app.state.config.DOCLING_DO_OCR = (
|
request.app.state.config.DOCLING_DO_OCR = (
|
||||||
form_data.DOCLING_DO_OCR
|
form_data.DOCLING_DO_OCR
|
||||||
if form_data.DOCLING_DO_OCR is not None
|
if form_data.DOCLING_DO_OCR is not None
|
||||||
|
|
@ -1104,6 +1135,7 @@ async def update_rag_config(
|
||||||
"EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
"EXTERNAL_DOCUMENT_LOADER_API_KEY": request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY,
|
||||||
"TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL,
|
"TIKA_SERVER_URL": request.app.state.config.TIKA_SERVER_URL,
|
||||||
"DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL,
|
"DOCLING_SERVER_URL": request.app.state.config.DOCLING_SERVER_URL,
|
||||||
|
"DOCLING_PARAMS": request.app.state.config.DOCLING_PARAMS,
|
||||||
"DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR,
|
"DOCLING_DO_OCR": request.app.state.config.DOCLING_DO_OCR,
|
||||||
"DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR,
|
"DOCLING_FORCE_OCR": request.app.state.config.DOCLING_FORCE_OCR,
|
||||||
"DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE,
|
"DOCLING_OCR_ENGINE": request.app.state.config.DOCLING_OCR_ENGINE,
|
||||||
|
|
@ -1522,6 +1554,7 @@ def process_file(
|
||||||
"picture_description_mode": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE,
|
"picture_description_mode": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_MODE,
|
||||||
"picture_description_local": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL,
|
"picture_description_local": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_LOCAL,
|
||||||
"picture_description_api": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_API,
|
"picture_description_api": request.app.state.config.DOCLING_PICTURE_DESCRIPTION_API,
|
||||||
|
**request.app.state.config.DOCLING_PARAMS,
|
||||||
},
|
},
|
||||||
PDF_EXTRACT_IMAGES=request.app.state.config.PDF_EXTRACT_IMAGES,
|
PDF_EXTRACT_IMAGES=request.app.state.config.PDF_EXTRACT_IMAGES,
|
||||||
DOCUMENT_INTELLIGENCE_ENDPOINT=request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
|
DOCUMENT_INTELLIGENCE_ENDPOINT=request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
|
||||||
|
|
@ -1680,49 +1713,6 @@ def process_text(
|
||||||
|
|
||||||
|
|
||||||
@router.post("/process/youtube")
|
@router.post("/process/youtube")
|
||||||
def process_youtube_video(
|
|
||||||
request: Request, form_data: ProcessUrlForm, user=Depends(get_verified_user)
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
collection_name = form_data.collection_name
|
|
||||||
if not collection_name:
|
|
||||||
collection_name = calculate_sha256_string(form_data.url)[:63]
|
|
||||||
|
|
||||||
loader = YoutubeLoader(
|
|
||||||
form_data.url,
|
|
||||||
language=request.app.state.config.YOUTUBE_LOADER_LANGUAGE,
|
|
||||||
proxy_url=request.app.state.config.YOUTUBE_LOADER_PROXY_URL,
|
|
||||||
)
|
|
||||||
|
|
||||||
docs = loader.load()
|
|
||||||
content = " ".join([doc.page_content for doc in docs])
|
|
||||||
log.debug(f"text_content: {content}")
|
|
||||||
|
|
||||||
save_docs_to_vector_db(
|
|
||||||
request, docs, collection_name, overwrite=True, user=user
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": True,
|
|
||||||
"collection_name": collection_name,
|
|
||||||
"filename": form_data.url,
|
|
||||||
"file": {
|
|
||||||
"data": {
|
|
||||||
"content": content,
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"name": form_data.url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
log.exception(e)
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=ERROR_MESSAGES.DEFAULT(e),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/process/web")
|
@router.post("/process/web")
|
||||||
def process_web(
|
def process_web(
|
||||||
request: Request, form_data: ProcessUrlForm, user=Depends(get_verified_user)
|
request: Request, form_data: ProcessUrlForm, user=Depends(get_verified_user)
|
||||||
|
|
@ -1732,19 +1722,16 @@ def process_web(
|
||||||
if not collection_name:
|
if not collection_name:
|
||||||
collection_name = calculate_sha256_string(form_data.url)[:63]
|
collection_name = calculate_sha256_string(form_data.url)[:63]
|
||||||
|
|
||||||
loader = get_web_loader(
|
content, docs = get_content_from_url(request, form_data.url)
|
||||||
form_data.url,
|
|
||||||
verify_ssl=request.app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION,
|
|
||||||
requests_per_second=request.app.state.config.WEB_LOADER_CONCURRENT_REQUESTS,
|
|
||||||
)
|
|
||||||
docs = loader.load()
|
|
||||||
content = " ".join([doc.page_content for doc in docs])
|
|
||||||
|
|
||||||
log.debug(f"text_content: {content}")
|
log.debug(f"text_content: {content}")
|
||||||
|
|
||||||
if not request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
|
if not request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
|
||||||
save_docs_to_vector_db(
|
save_docs_to_vector_db(
|
||||||
request, docs, collection_name, overwrite=True, user=user
|
request,
|
||||||
|
docs,
|
||||||
|
collection_name,
|
||||||
|
overwrite=True,
|
||||||
|
user=user,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
collection_name = None
|
collection_name = None
|
||||||
|
|
@ -2047,7 +2034,7 @@ async def process_web_search(
|
||||||
result_items = []
|
result_items = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.info(
|
logging.debug(
|
||||||
f"trying to web search with {request.app.state.config.WEB_SEARCH_ENGINE, form_data.queries}"
|
f"trying to web search with {request.app.state.config.WEB_SEARCH_ENGINE, form_data.queries}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2081,6 +2068,12 @@ async def process_web_search(
|
||||||
detail=ERROR_MESSAGES.WEB_SEARCH_ERROR(e),
|
detail=ERROR_MESSAGES.WEB_SEARCH_ERROR(e),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(urls) == 0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=ERROR_MESSAGES.DEFAULT("No results found from web search"),
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if request.app.state.config.BYPASS_WEB_SEARCH_WEB_LOADER:
|
if request.app.state.config.BYPASS_WEB_SEARCH_WEB_LOADER:
|
||||||
search_results = [
|
search_results = [
|
||||||
|
|
|
||||||
|
|
@ -356,7 +356,7 @@ async def join_note(sid, data):
|
||||||
await sio.enter_room(sid, f"note:{note.id}")
|
await sio.enter_room(sid, f"note:{note.id}")
|
||||||
|
|
||||||
|
|
||||||
@sio.on("channel-events")
|
@sio.on("events:channel")
|
||||||
async def channel_events(sid, data):
|
async def channel_events(sid, data):
|
||||||
room = f"channel:{data['channel_id']}"
|
room = f"channel:{data['channel_id']}"
|
||||||
participants = sio.manager.get_participants(
|
participants = sio.manager.get_participants(
|
||||||
|
|
@ -373,7 +373,7 @@ async def channel_events(sid, data):
|
||||||
|
|
||||||
if event_type == "typing":
|
if event_type == "typing":
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"channel-events",
|
"events:channel",
|
||||||
{
|
{
|
||||||
"channel_id": data["channel_id"],
|
"channel_id": data["channel_id"],
|
||||||
"message_id": data.get("message_id", None),
|
"message_id": data.get("message_id", None),
|
||||||
|
|
@ -653,12 +653,15 @@ def get_event_emitter(request_info, update_db=True):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
chat_id = request_info.get("chat_id", None)
|
||||||
|
message_id = request_info.get("message_id", None)
|
||||||
|
|
||||||
emit_tasks = [
|
emit_tasks = [
|
||||||
sio.emit(
|
sio.emit(
|
||||||
"chat-events",
|
"events",
|
||||||
{
|
{
|
||||||
"chat_id": request_info.get("chat_id", None),
|
"chat_id": chat_id,
|
||||||
"message_id": request_info.get("message_id", None),
|
"message_id": message_id,
|
||||||
"data": event_data,
|
"data": event_data,
|
||||||
},
|
},
|
||||||
to=session_id,
|
to=session_id,
|
||||||
|
|
@ -667,8 +670,11 @@ def get_event_emitter(request_info, update_db=True):
|
||||||
]
|
]
|
||||||
|
|
||||||
await asyncio.gather(*emit_tasks)
|
await asyncio.gather(*emit_tasks)
|
||||||
|
if (
|
||||||
if update_db:
|
update_db
|
||||||
|
and message_id
|
||||||
|
and not request_info.get("chat_id", "").startswith("local:")
|
||||||
|
):
|
||||||
if "type" in event_data and event_data["type"] == "status":
|
if "type" in event_data and event_data["type"] == "status":
|
||||||
Chats.add_message_status_to_chat_by_id_and_message_id(
|
Chats.add_message_status_to_chat_by_id_and_message_id(
|
||||||
request_info["chat_id"],
|
request_info["chat_id"],
|
||||||
|
|
@ -764,7 +770,7 @@ def get_event_emitter(request_info, update_db=True):
|
||||||
def get_event_call(request_info):
|
def get_event_call(request_info):
|
||||||
async def __event_caller__(event_data):
|
async def __event_caller__(event_data):
|
||||||
response = await sio.call(
|
response = await sio.call(
|
||||||
"chat-events",
|
"events",
|
||||||
{
|
{
|
||||||
"chat_id": request_info.get("chat_id", None),
|
"chat_id": request_info.get("chat_id", None),
|
||||||
"message_id": request_info.get("message_id", None),
|
"message_id": request_info.get("message_id", None),
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,10 @@ async def stop_task(redis, task_id: str):
|
||||||
# Task successfully canceled
|
# Task successfully canceled
|
||||||
return {"status": True, "message": f"Task {task_id} successfully stopped."}
|
return {"status": True, "message": f"Task {task_id} successfully stopped."}
|
||||||
|
|
||||||
return {"status": False, "message": f"Failed to stop task {task_id}."}
|
if task.cancelled() or task.done():
|
||||||
|
return {"status": True, "message": f"Task {task_id} successfully cancelled."}
|
||||||
|
|
||||||
|
return {"status": True, "message": f"Cancellation requested for {task_id}."}
|
||||||
|
|
||||||
|
|
||||||
async def stop_item_tasks(redis: Redis, item_id: str):
|
async def stop_item_tasks(redis: Redis, item_id: str):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import hmac
|
||||||
import hashlib
|
import hashlib
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
|
|
@ -38,11 +38,8 @@ from open_webui.env import (
|
||||||
|
|
||||||
from fastapi import BackgroundTasks, Depends, HTTPException, Request, Response, status
|
from fastapi import BackgroundTasks, Depends, HTTPException, Request, Response, status
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from passlib.context import CryptContext
|
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger("passlib").setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(SRC_LOG_LEVELS["OAUTH"])
|
log.setLevel(SRC_LOG_LEVELS["OAUTH"])
|
||||||
|
|
||||||
|
|
@ -155,17 +152,23 @@ def get_license_data(app, key):
|
||||||
|
|
||||||
|
|
||||||
bearer_security = HTTPBearer(auto_error=False)
|
bearer_security = HTTPBearer(auto_error=False)
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
||||||
|
|
||||||
|
|
||||||
def verify_password(plain_password, hashed_password):
|
def get_password_hash(password: str) -> str:
|
||||||
|
"""Hash a password using bcrypt"""
|
||||||
|
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||||
|
"""Verify a password against its hash"""
|
||||||
return (
|
return (
|
||||||
pwd_context.verify(plain_password, hashed_password) if hashed_password else None
|
bcrypt.checkpw(
|
||||||
|
plain_password.encode("utf-8"),
|
||||||
|
hashed_password.encode("utf-8"),
|
||||||
|
)
|
||||||
|
if hashed_password
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_password_hash(password):
|
|
||||||
return pwd_context.hash(password)
|
|
||||||
|
|
||||||
|
|
||||||
def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
|
def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ async def generate_direct_chat_completion(
|
||||||
event_caller = get_event_call(metadata)
|
event_caller = get_event_call(metadata)
|
||||||
|
|
||||||
channel = f"{user_id}:{session_id}:{request_id}"
|
channel = f"{user_id}:{session_id}:{request_id}"
|
||||||
|
logging.info(f"WebSocket channel: {channel}")
|
||||||
|
|
||||||
if form_data.get("stream"):
|
if form_data.get("stream"):
|
||||||
q = asyncio.Queue()
|
q = asyncio.Queue()
|
||||||
|
|
@ -121,7 +122,10 @@ async def generate_direct_chat_completion(
|
||||||
|
|
||||||
yield f"data: {json.dumps(data)}\n\n"
|
yield f"data: {json.dumps(data)}\n\n"
|
||||||
elif isinstance(data, str):
|
elif isinstance(data, str):
|
||||||
yield data
|
if "data:" in data:
|
||||||
|
yield f"{data}\n\n"
|
||||||
|
else:
|
||||||
|
yield f"data: {data}\n\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(f"Error in event generator: {e}")
|
log.debug(f"Error in event generator: {e}")
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,10 @@ from open_webui.routers.tasks import (
|
||||||
generate_image_prompt,
|
generate_image_prompt,
|
||||||
generate_chat_tags,
|
generate_chat_tags,
|
||||||
)
|
)
|
||||||
from open_webui.routers.retrieval import process_web_search, SearchForm
|
from open_webui.routers.retrieval import (
|
||||||
|
process_web_search,
|
||||||
|
SearchForm,
|
||||||
|
)
|
||||||
from open_webui.routers.images import (
|
from open_webui.routers.images import (
|
||||||
load_b64_image_data,
|
load_b64_image_data,
|
||||||
image_generations,
|
image_generations,
|
||||||
|
|
@ -76,14 +79,17 @@ from open_webui.utils.task import (
|
||||||
)
|
)
|
||||||
from open_webui.utils.misc import (
|
from open_webui.utils.misc import (
|
||||||
deep_update,
|
deep_update,
|
||||||
|
extract_urls,
|
||||||
get_message_list,
|
get_message_list,
|
||||||
add_or_update_system_message,
|
add_or_update_system_message,
|
||||||
add_or_update_user_message,
|
add_or_update_user_message,
|
||||||
get_last_user_message,
|
get_last_user_message,
|
||||||
|
get_last_user_message_item,
|
||||||
get_last_assistant_message,
|
get_last_assistant_message,
|
||||||
get_system_message,
|
get_system_message,
|
||||||
prepend_to_first_user_message_content,
|
prepend_to_first_user_message_content,
|
||||||
convert_logit_bias_input_to_json,
|
convert_logit_bias_input_to_json,
|
||||||
|
get_content_from_message,
|
||||||
)
|
)
|
||||||
from open_webui.utils.tools import get_tools
|
from open_webui.utils.tools import get_tools
|
||||||
from open_webui.utils.plugin import load_function_module_by_id
|
from open_webui.utils.plugin import load_function_module_by_id
|
||||||
|
|
@ -147,7 +153,7 @@ def process_tool_result(
|
||||||
if isinstance(tool_result, HTMLResponse):
|
if isinstance(tool_result, HTMLResponse):
|
||||||
content_disposition = tool_result.headers.get("Content-Disposition", "")
|
content_disposition = tool_result.headers.get("Content-Disposition", "")
|
||||||
if "inline" in content_disposition:
|
if "inline" in content_disposition:
|
||||||
content = tool_result.body.decode("utf-8")
|
content = tool_result.body.decode("utf-8", "replace")
|
||||||
tool_result_embeds.append(content)
|
tool_result_embeds.append(content)
|
||||||
|
|
||||||
if 200 <= tool_result.status_code < 300:
|
if 200 <= tool_result.status_code < 300:
|
||||||
|
|
@ -175,7 +181,7 @@ def process_tool_result(
|
||||||
"message": f"{tool_function_name}: Unexpected status code {tool_result.status_code} from embedded UI result.",
|
"message": f"{tool_function_name}: Unexpected status code {tool_result.status_code} from embedded UI result.",
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
tool_result = tool_result.body.decode("utf-8")
|
tool_result = tool_result.body.decode("utf-8", "replace")
|
||||||
|
|
||||||
elif (tool_type == "external" and isinstance(tool_result, tuple)) or (
|
elif (tool_type == "external" and isinstance(tool_result, tuple)) or (
|
||||||
direct_tool and isinstance(tool_result, list) and len(tool_result) == 2
|
direct_tool and isinstance(tool_result, list) and len(tool_result) == 2
|
||||||
|
|
@ -283,7 +289,7 @@ async def chat_completion_tools_handler(
|
||||||
content = None
|
content = None
|
||||||
if hasattr(response, "body_iterator"):
|
if hasattr(response, "body_iterator"):
|
||||||
async for chunk in response.body_iterator:
|
async for chunk in response.body_iterator:
|
||||||
data = json.loads(chunk.decode("utf-8"))
|
data = json.loads(chunk.decode("utf-8", "replace"))
|
||||||
content = data["choices"][0]["message"]["content"]
|
content = data["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
# Cleanup any remaining background tasks if necessary
|
# Cleanup any remaining background tasks if necessary
|
||||||
|
|
@ -298,7 +304,7 @@ async def chat_completion_tools_handler(
|
||||||
|
|
||||||
recent_messages = messages[-4:] if len(messages) > 4 else messages
|
recent_messages = messages[-4:] if len(messages) > 4 else messages
|
||||||
chat_history = "\n".join(
|
chat_history = "\n".join(
|
||||||
f"{message['role'].upper()}: \"\"\"{message['content']}\"\"\""
|
f"{message['role'].upper()}: \"\"\"{get_content_from_message(message)}\"\"\""
|
||||||
for message in recent_messages
|
for message in recent_messages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -821,7 +827,11 @@ async def chat_completion_files_handler(
|
||||||
|
|
||||||
if files := body.get("metadata", {}).get("files", None):
|
if files := body.get("metadata", {}).get("files", None):
|
||||||
# Check if all files are in full context mode
|
# Check if all files are in full context mode
|
||||||
all_full_context = all(item.get("context") == "full" for item in files)
|
all_full_context = all(
|
||||||
|
item.get("context") == "full"
|
||||||
|
for item in files
|
||||||
|
if item.get("type") == "file"
|
||||||
|
)
|
||||||
|
|
||||||
queries = []
|
queries = []
|
||||||
if not all_full_context:
|
if not all_full_context:
|
||||||
|
|
@ -853,10 +863,6 @@ async def chat_completion_files_handler(
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if len(queries) == 0:
|
|
||||||
queries = [get_last_user_message(body["messages"])]
|
|
||||||
|
|
||||||
if not all_full_context:
|
|
||||||
await __event_emitter__(
|
await __event_emitter__(
|
||||||
{
|
{
|
||||||
"type": "status",
|
"type": "status",
|
||||||
|
|
@ -868,6 +874,9 @@ async def chat_completion_files_handler(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(queries) == 0:
|
||||||
|
queries = [get_last_user_message(body["messages"])]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Offload get_sources_from_items to a separate thread
|
# Offload get_sources_from_items to a separate thread
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
@ -906,7 +915,6 @@ async def chat_completion_files_handler(
|
||||||
log.debug(f"rag_contexts:sources: {sources}")
|
log.debug(f"rag_contexts:sources: {sources}")
|
||||||
|
|
||||||
unique_ids = set()
|
unique_ids = set()
|
||||||
|
|
||||||
for source in sources or []:
|
for source in sources or []:
|
||||||
if not source or len(source.keys()) == 0:
|
if not source or len(source.keys()) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
@ -925,7 +933,6 @@ async def chat_completion_files_handler(
|
||||||
unique_ids.add(_id)
|
unique_ids.add(_id)
|
||||||
|
|
||||||
sources_count = len(unique_ids)
|
sources_count = len(unique_ids)
|
||||||
|
|
||||||
await __event_emitter__(
|
await __event_emitter__(
|
||||||
{
|
{
|
||||||
"type": "status",
|
"type": "status",
|
||||||
|
|
@ -999,11 +1006,11 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||||
log.debug(f"form_data: {form_data}")
|
log.debug(f"form_data: {form_data}")
|
||||||
|
|
||||||
system_message = get_system_message(form_data.get("messages", []))
|
system_message = get_system_message(form_data.get("messages", []))
|
||||||
if system_message:
|
if system_message: # Chat Controls/User Settings
|
||||||
try:
|
try:
|
||||||
form_data = apply_system_prompt_to_body(
|
form_data = apply_system_prompt_to_body(
|
||||||
system_message.get("content"), form_data, metadata, user
|
system_message.get("content"), form_data, metadata, user, replace=True
|
||||||
)
|
) # Required to handle system prompt variables
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -1168,8 +1175,28 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||||
tool_ids = form_data.pop("tool_ids", None)
|
tool_ids = form_data.pop("tool_ids", None)
|
||||||
files = form_data.pop("files", None)
|
files = form_data.pop("files", None)
|
||||||
|
|
||||||
# Remove files duplicates
|
prompt = get_last_user_message(form_data["messages"])
|
||||||
|
# TODO: re-enable URL extraction from prompt
|
||||||
|
# urls = []
|
||||||
|
# if prompt and len(prompt or "") < 500 and (not files or len(files) == 0):
|
||||||
|
# urls = extract_urls(prompt)
|
||||||
|
|
||||||
if files:
|
if files:
|
||||||
|
if not files:
|
||||||
|
files = []
|
||||||
|
|
||||||
|
for file_item in files:
|
||||||
|
if file_item.get("type", "file") == "folder":
|
||||||
|
# Get folder files
|
||||||
|
folder_id = file_item.get("id", None)
|
||||||
|
if folder_id:
|
||||||
|
folder = Folders.get_folder_by_id_and_user_id(folder_id, user.id)
|
||||||
|
if folder and folder.data and "files" in folder.data:
|
||||||
|
files = [f for f in files if f.get("id", None) != folder_id]
|
||||||
|
files = [*files, *folder.data["files"]]
|
||||||
|
|
||||||
|
# files = [*files, *[{"type": "url", "url": url, "name": url} for url in urls]]
|
||||||
|
# Remove duplicate files based on their content
|
||||||
files = list({json.dumps(f, sort_keys=True): f for f in files}.values())
|
files = list({json.dumps(f, sort_keys=True): f for f in files}.values())
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
|
|
@ -1261,9 +1288,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||||
|
|
||||||
def make_tool_function(client, function_name):
|
def make_tool_function(client, function_name):
|
||||||
async def tool_function(**kwargs):
|
async def tool_function(**kwargs):
|
||||||
print(kwargs)
|
|
||||||
print(client)
|
|
||||||
print(await client.list_tool_specs())
|
|
||||||
return await client.call_tool(
|
return await client.call_tool(
|
||||||
function_name,
|
function_name,
|
||||||
function_args=kwargs,
|
function_args=kwargs,
|
||||||
|
|
@ -1370,8 +1394,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||||
)
|
)
|
||||||
|
|
||||||
context_string = context_string.strip()
|
context_string = context_string.strip()
|
||||||
|
|
||||||
prompt = get_last_user_message(form_data["messages"])
|
|
||||||
if prompt is None:
|
if prompt is None:
|
||||||
raise Exception("No user message found")
|
raise Exception("No user message found")
|
||||||
|
|
||||||
|
|
@ -1410,10 +1432,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Final form_data:", form_data)
|
|
||||||
print("Final metadata:", metadata)
|
|
||||||
print("Final events:", events)
|
|
||||||
|
|
||||||
return form_data, metadata, events
|
return form_data, metadata, events
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1421,10 +1439,13 @@ async def process_chat_response(
|
||||||
request, response, form_data, user, metadata, model, events, tasks
|
request, response, form_data, user, metadata, model, events, tasks
|
||||||
):
|
):
|
||||||
async def background_tasks_handler():
|
async def background_tasks_handler():
|
||||||
|
message = None
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
if "chat_id" in metadata and not metadata["chat_id"].startswith("local:"):
|
||||||
messages_map = Chats.get_messages_map_by_chat_id(metadata["chat_id"])
|
messages_map = Chats.get_messages_map_by_chat_id(metadata["chat_id"])
|
||||||
message = messages_map.get(metadata["message_id"]) if messages_map else None
|
message = messages_map.get(metadata["message_id"]) if messages_map else None
|
||||||
|
|
||||||
if message:
|
|
||||||
message_list = get_message_list(messages_map, metadata["message_id"])
|
message_list = get_message_list(messages_map, metadata["message_id"])
|
||||||
|
|
||||||
# Remove details tags and files from the messages.
|
# Remove details tags and files from the messages.
|
||||||
|
|
@ -1457,7 +1478,14 @@ async def process_chat_response(
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# Local temp chat, get the model and message from the form_data
|
||||||
|
message = get_last_user_message_item(form_data.get("messages", []))
|
||||||
|
messages = form_data.get("messages", [])
|
||||||
|
if message:
|
||||||
|
message["model"] = form_data.get("model")
|
||||||
|
|
||||||
|
if message and "model" in message:
|
||||||
if tasks and messages:
|
if tasks and messages:
|
||||||
if (
|
if (
|
||||||
TASKS.FOLLOW_UP_GENERATION in tasks
|
TASKS.FOLLOW_UP_GENERATION in tasks
|
||||||
|
|
@ -1476,10 +1504,12 @@ async def process_chat_response(
|
||||||
|
|
||||||
if res and isinstance(res, dict):
|
if res and isinstance(res, dict):
|
||||||
if len(res.get("choices", [])) == 1:
|
if len(res.get("choices", [])) == 1:
|
||||||
follow_ups_string = (
|
response_message = res.get("choices", [])[0].get(
|
||||||
res.get("choices", [])[0]
|
"message", {}
|
||||||
.get("message", {})
|
)
|
||||||
.get("content", "")
|
|
||||||
|
follow_ups_string = response_message.get(
|
||||||
|
"content", response_message.get("reasoning_content", "")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
follow_ups_string = ""
|
follow_ups_string = ""
|
||||||
|
|
@ -1493,15 +1523,6 @@ async def process_chat_response(
|
||||||
follow_ups = json.loads(follow_ups_string).get(
|
follow_ups = json.loads(follow_ups_string).get(
|
||||||
"follow_ups", []
|
"follow_ups", []
|
||||||
)
|
)
|
||||||
|
|
||||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
|
||||||
metadata["chat_id"],
|
|
||||||
metadata["message_id"],
|
|
||||||
{
|
|
||||||
"followUps": follow_ups,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
await event_emitter(
|
await event_emitter(
|
||||||
{
|
{
|
||||||
"type": "chat:message:follow_ups",
|
"type": "chat:message:follow_ups",
|
||||||
|
|
@ -1510,10 +1531,26 @@ async def process_chat_response(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not metadata.get("chat_id", "").startswith("local:"):
|
||||||
|
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||||
|
metadata["chat_id"],
|
||||||
|
metadata["message_id"],
|
||||||
|
{
|
||||||
|
"followUps": follow_ups,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if TASKS.TITLE_GENERATION in tasks:
|
if not metadata.get("chat_id", "").startswith(
|
||||||
|
"local:"
|
||||||
|
): # Only update titles and tags for non-temp chats
|
||||||
|
if (
|
||||||
|
TASKS.TITLE_GENERATION in tasks
|
||||||
|
and tasks[TASKS.TITLE_GENERATION]
|
||||||
|
):
|
||||||
user_message = get_last_user_message(messages)
|
user_message = get_last_user_message(messages)
|
||||||
if user_message and len(user_message) > 100:
|
if user_message and len(user_message) > 100:
|
||||||
user_message = user_message[:100] + "..."
|
user_message = user_message[:100] + "..."
|
||||||
|
|
@ -1532,12 +1569,16 @@ async def process_chat_response(
|
||||||
|
|
||||||
if res and isinstance(res, dict):
|
if res and isinstance(res, dict):
|
||||||
if len(res.get("choices", [])) == 1:
|
if len(res.get("choices", [])) == 1:
|
||||||
title_string = (
|
response_message = res.get("choices", [])[0].get(
|
||||||
res.get("choices", [])[0]
|
"message", {}
|
||||||
.get("message", {})
|
|
||||||
.get(
|
|
||||||
"content", message.get("content", user_message)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
title_string = response_message.get(
|
||||||
|
"content",
|
||||||
|
response_message.get(
|
||||||
|
"reasoning_content",
|
||||||
|
message.get("content", user_message),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
title_string = ""
|
title_string = ""
|
||||||
|
|
@ -1556,7 +1597,9 @@ async def process_chat_response(
|
||||||
if not title:
|
if not title:
|
||||||
title = messages[0].get("content", user_message)
|
title = messages[0].get("content", user_message)
|
||||||
|
|
||||||
Chats.update_chat_title_by_id(metadata["chat_id"], title)
|
Chats.update_chat_title_by_id(
|
||||||
|
metadata["chat_id"], title
|
||||||
|
)
|
||||||
|
|
||||||
await event_emitter(
|
await event_emitter(
|
||||||
{
|
{
|
||||||
|
|
@ -1589,10 +1632,13 @@ async def process_chat_response(
|
||||||
|
|
||||||
if res and isinstance(res, dict):
|
if res and isinstance(res, dict):
|
||||||
if len(res.get("choices", [])) == 1:
|
if len(res.get("choices", [])) == 1:
|
||||||
tags_string = (
|
response_message = res.get("choices", [])[0].get(
|
||||||
res.get("choices", [])[0]
|
"message", {}
|
||||||
.get("message", {})
|
)
|
||||||
.get("content", "")
|
|
||||||
|
tags_string = response_message.get(
|
||||||
|
"content",
|
||||||
|
response_message.get("reasoning_content", ""),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tags_string = ""
|
tags_string = ""
|
||||||
|
|
@ -1642,7 +1688,9 @@ async def process_chat_response(
|
||||||
response.body, bytes
|
response.body, bytes
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
response_data = json.loads(response.body.decode("utf-8"))
|
response_data = json.loads(
|
||||||
|
response.body.decode("utf-8", "replace")
|
||||||
|
)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
response_data = {
|
response_data = {
|
||||||
"error": {"detail": "Invalid JSON response"}
|
"error": {"detail": "Invalid JSON response"}
|
||||||
|
|
@ -2276,7 +2324,11 @@ async def process_chat_response(
|
||||||
last_delta_data = None
|
last_delta_data = None
|
||||||
|
|
||||||
async for line in response.body_iterator:
|
async for line in response.body_iterator:
|
||||||
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
line = (
|
||||||
|
line.decode("utf-8", "replace")
|
||||||
|
if isinstance(line, bytes)
|
||||||
|
else line
|
||||||
|
)
|
||||||
data = line
|
data = line
|
||||||
|
|
||||||
# Skip empty lines
|
# Skip empty lines
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,14 @@ def update_message_content(message: dict, content: str, append: bool = True) ->
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
def replace_system_message_content(content: str, messages: list[dict]) -> dict:
|
||||||
|
for message in messages:
|
||||||
|
if message["role"] == "system":
|
||||||
|
message["content"] = content
|
||||||
|
break
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
def add_or_update_system_message(
|
def add_or_update_system_message(
|
||||||
content: str, messages: list[dict], append: bool = False
|
content: str, messages: list[dict], append: bool = False
|
||||||
):
|
):
|
||||||
|
|
@ -523,3 +531,11 @@ def throttle(interval: float = 10.0):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def extract_urls(text: str) -> list[str]:
|
||||||
|
# Regex pattern to match URLs
|
||||||
|
url_pattern = re.compile(
|
||||||
|
r"(https?://[^\s]+)", re.IGNORECASE
|
||||||
|
) # Matches http and https URLs
|
||||||
|
return url_pattern.findall(text)
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ from open_webui.env import (
|
||||||
WEBUI_AUTH_COOKIE_SAME_SITE,
|
WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
WEBUI_AUTH_COOKIE_SECURE,
|
WEBUI_AUTH_COOKIE_SECURE,
|
||||||
ENABLE_OAUTH_ID_TOKEN_COOKIE,
|
ENABLE_OAUTH_ID_TOKEN_COOKIE,
|
||||||
|
ENABLE_OAUTH_EMAIL_FALLBACK,
|
||||||
OAUTH_CLIENT_INFO_ENCRYPTION_KEY,
|
OAUTH_CLIENT_INFO_ENCRYPTION_KEY,
|
||||||
)
|
)
|
||||||
from open_webui.utils.misc import parse_duration
|
from open_webui.utils.misc import parse_duration
|
||||||
|
|
@ -82,6 +83,8 @@ class OAuthClientInformationFull(OAuthClientMetadata):
|
||||||
client_id_issued_at: int | None = None
|
client_id_issued_at: int | None = None
|
||||||
client_secret_expires_at: int | None = None
|
client_secret_expires_at: int | None = None
|
||||||
|
|
||||||
|
server_metadata: Optional[OAuthMetadata] = None # Fetched from the OAuth server
|
||||||
|
|
||||||
|
|
||||||
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
|
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
|
||||||
|
|
||||||
|
|
@ -296,6 +299,7 @@ async def get_oauth_client_info_with_dynamic_client_registration(
|
||||||
{
|
{
|
||||||
**registration_response_json,
|
**registration_response_json,
|
||||||
**{"issuer": oauth_server_metadata_url},
|
**{"issuer": oauth_server_metadata_url},
|
||||||
|
**{"server_metadata": oauth_server_metadata},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
log.info(
|
log.info(
|
||||||
|
|
@ -331,20 +335,34 @@ class OAuthClientManager:
|
||||||
self.clients = {}
|
self.clients = {}
|
||||||
|
|
||||||
def add_client(self, client_id, oauth_client_info: OAuthClientInformationFull):
|
def add_client(self, client_id, oauth_client_info: OAuthClientInformationFull):
|
||||||
self.clients[client_id] = {
|
kwargs = {
|
||||||
"client": self.oauth.register(
|
"name": client_id,
|
||||||
name=client_id,
|
"client_id": oauth_client_info.client_id,
|
||||||
client_id=oauth_client_info.client_id,
|
"client_secret": oauth_client_info.client_secret,
|
||||||
client_secret=oauth_client_info.client_secret,
|
"client_kwargs": (
|
||||||
client_kwargs=(
|
{"scope": oauth_client_info.scope} if oauth_client_info.scope else {}
|
||||||
{"scope": oauth_client_info.scope}
|
|
||||||
if oauth_client_info.scope
|
|
||||||
else {}
|
|
||||||
),
|
),
|
||||||
server_metadata_url=(
|
"server_metadata_url": (
|
||||||
oauth_client_info.issuer if oauth_client_info.issuer else None
|
oauth_client_info.issuer if oauth_client_info.issuer else None
|
||||||
),
|
),
|
||||||
),
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
oauth_client_info.server_metadata
|
||||||
|
and oauth_client_info.server_metadata.code_challenge_methods_supported
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
isinstance(
|
||||||
|
oauth_client_info.server_metadata.code_challenge_methods_supported,
|
||||||
|
list,
|
||||||
|
)
|
||||||
|
and "S256"
|
||||||
|
in oauth_client_info.server_metadata.code_challenge_methods_supported
|
||||||
|
):
|
||||||
|
kwargs["code_challenge_method"] = "S256"
|
||||||
|
|
||||||
|
self.clients[client_id] = {
|
||||||
|
"client": self.oauth.register(**kwargs),
|
||||||
"client_info": oauth_client_info,
|
"client_info": oauth_client_info,
|
||||||
}
|
}
|
||||||
return self.clients[client_id]
|
return self.clients[client_id]
|
||||||
|
|
@ -367,8 +385,8 @@ class OAuthClientManager:
|
||||||
if client_id in self.clients:
|
if client_id in self.clients:
|
||||||
client = self.clients[client_id]
|
client = self.clients[client_id]
|
||||||
return (
|
return (
|
||||||
client.server_metadata_url
|
client._server_metadata_url
|
||||||
if hasattr(client, "server_metadata_url")
|
if hasattr(client, "_server_metadata_url")
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
@ -560,7 +578,17 @@ class OAuthClientManager:
|
||||||
|
|
||||||
error_message = None
|
error_message = None
|
||||||
try:
|
try:
|
||||||
token = await client.authorize_access_token(request)
|
client_info = self.get_client_info(client_id)
|
||||||
|
token_params = {}
|
||||||
|
if (
|
||||||
|
client_info
|
||||||
|
and hasattr(client_info, "client_id")
|
||||||
|
and hasattr(client_info, "client_secret")
|
||||||
|
):
|
||||||
|
token_params["client_id"] = client_info.client_id
|
||||||
|
token_params["client_secret"] = client_info.client_secret
|
||||||
|
|
||||||
|
token = await client.authorize_access_token(request, **token_params)
|
||||||
if token:
|
if token:
|
||||||
try:
|
try:
|
||||||
# Add timestamp for tracking
|
# Add timestamp for tracking
|
||||||
|
|
@ -615,8 +643,14 @@ class OAuthManager:
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self._clients = {}
|
self._clients = {}
|
||||||
for _, provider_config in OAUTH_PROVIDERS.items():
|
|
||||||
provider_config["register"](self.oauth)
|
for name, provider_config in OAUTH_PROVIDERS.items():
|
||||||
|
if "register" not in provider_config:
|
||||||
|
log.error(f"OAuth provider {name} missing register function")
|
||||||
|
continue
|
||||||
|
|
||||||
|
client = provider_config["register"](self.oauth)
|
||||||
|
self._clients[name] = client
|
||||||
|
|
||||||
def get_client(self, provider_name):
|
def get_client(self, provider_name):
|
||||||
if provider_name not in self._clients:
|
if provider_name not in self._clients:
|
||||||
|
|
@ -627,8 +661,8 @@ class OAuthManager:
|
||||||
if provider_name in self._clients:
|
if provider_name in self._clients:
|
||||||
client = self._clients[provider_name]
|
client = self._clients[provider_name]
|
||||||
return (
|
return (
|
||||||
client.server_metadata_url
|
client._server_metadata_url
|
||||||
if hasattr(client, "server_metadata_url")
|
if hasattr(client, "_server_metadata_url")
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
@ -1147,6 +1181,8 @@ class OAuthManager:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"Error fetching GitHub email: {e}")
|
log.warning(f"Error fetching GitHub email: {e}")
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
elif ENABLE_OAUTH_EMAIL_FALLBACK:
|
||||||
|
email = f"{provider_sub}.local"
|
||||||
else:
|
else:
|
||||||
log.warning(f"OAuth callback failed, email is missing: {user_data}")
|
log.warning(f"OAuth callback failed, email is missing: {user_data}")
|
||||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from open_webui.utils.task import prompt_template, prompt_variables_template
|
||||||
from open_webui.utils.misc import (
|
from open_webui.utils.misc import (
|
||||||
deep_update,
|
deep_update,
|
||||||
add_or_update_system_message,
|
add_or_update_system_message,
|
||||||
|
replace_system_message_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
@ -10,7 +11,11 @@ import json
|
||||||
|
|
||||||
# inplace function: form_data is modified
|
# inplace function: form_data is modified
|
||||||
def apply_system_prompt_to_body(
|
def apply_system_prompt_to_body(
|
||||||
system: Optional[str], form_data: dict, metadata: Optional[dict] = None, user=None
|
system: Optional[str],
|
||||||
|
form_data: dict,
|
||||||
|
metadata: Optional[dict] = None,
|
||||||
|
user=None,
|
||||||
|
replace: bool = False,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if not system:
|
if not system:
|
||||||
return form_data
|
return form_data
|
||||||
|
|
@ -24,9 +29,15 @@ def apply_system_prompt_to_body(
|
||||||
# Legacy (API Usage)
|
# Legacy (API Usage)
|
||||||
system = prompt_template(system, user)
|
system = prompt_template(system, user)
|
||||||
|
|
||||||
|
if replace:
|
||||||
|
form_data["messages"] = replace_system_message_content(
|
||||||
|
system, form_data.get("messages", [])
|
||||||
|
)
|
||||||
|
else:
|
||||||
form_data["messages"] = add_or_update_system_message(
|
form_data["messages"] = add_or_update_system_message(
|
||||||
system, form_data.get("messages", [])
|
system, form_data.get("messages", [])
|
||||||
)
|
)
|
||||||
|
|
||||||
return form_data
|
return form_data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -748,10 +748,6 @@ async def execute_tool_server(
|
||||||
if operation.get("requestBody", {}).get("content"):
|
if operation.get("requestBody", {}).get("content"):
|
||||||
if params:
|
if params:
|
||||||
body_params = params
|
body_params = params
|
||||||
else:
|
|
||||||
raise Exception(
|
|
||||||
f"Request body expected for operation '{name}' but none found."
|
|
||||||
)
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession(
|
async with aiohttp.ClientSession(
|
||||||
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
fastapi==0.115.7
|
fastapi==0.118.0
|
||||||
uvicorn[standard]==0.35.0
|
uvicorn[standard]==0.37.0
|
||||||
pydantic==2.11.7
|
pydantic==2.11.9
|
||||||
python-multipart==0.0.20
|
python-multipart==0.0.20
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
|
|
||||||
python-socketio==5.13.0
|
python-socketio==5.13.0
|
||||||
python-jose==3.4.0
|
python-jose==3.4.0
|
||||||
passlib[bcrypt]==1.7.4
|
|
||||||
cryptography
|
cryptography
|
||||||
bcrypt==4.3.0
|
bcrypt==5.0.0
|
||||||
argon2-cffi==25.1.0
|
argon2-cffi==25.1.0
|
||||||
PyJWT[crypto]==2.10.1
|
PyJWT[crypto]==2.10.1
|
||||||
authlib==1.6.3
|
authlib==1.6.3
|
||||||
|
|
@ -30,14 +29,6 @@ peewee-migrate==1.12.2
|
||||||
pycrdt==0.12.25
|
pycrdt==0.12.25
|
||||||
redis
|
redis
|
||||||
|
|
||||||
pymongo
|
|
||||||
|
|
||||||
psycopg2-binary==2.9.10
|
|
||||||
pgvector==0.4.1
|
|
||||||
|
|
||||||
PyMySQL==1.1.1
|
|
||||||
boto3==1.40.5
|
|
||||||
|
|
||||||
APScheduler==3.10.4
|
APScheduler==3.10.4
|
||||||
RestrictedPython==8.0
|
RestrictedPython==8.0
|
||||||
|
|
||||||
|
|
@ -57,25 +48,15 @@ langchain==0.3.27
|
||||||
langchain-community==0.3.29
|
langchain-community==0.3.29
|
||||||
|
|
||||||
fake-useragent==2.2.0
|
fake-useragent==2.2.0
|
||||||
chromadb==1.0.20
|
chromadb==1.1.0
|
||||||
opensearch-py==2.8.0
|
opensearch-py==2.8.0
|
||||||
|
|
||||||
pymilvus==2.5.0
|
|
||||||
qdrant-client==1.14.3
|
|
||||||
playwright==1.49.1 # Caution: version must match docker-compose.playwright.yaml
|
|
||||||
elasticsearch==9.1.0
|
|
||||||
pinecone==6.0.2
|
|
||||||
oracledb==3.2.0
|
|
||||||
|
|
||||||
av==14.0.1 # Caution: Set due to FATAL FIPS SELFTEST FAILURE, see discussion https://github.com/open-webui/open-webui/discussions/15720
|
|
||||||
transformers
|
transformers
|
||||||
sentence-transformers==5.1.1
|
sentence-transformers==5.1.1
|
||||||
accelerate
|
accelerate
|
||||||
pyarrow==20.0.0 # fix: pin pyarrow version to 20 for rpi compatibility #15897
|
pyarrow==20.0.0 # fix: pin pyarrow version to 20 for rpi compatibility #15897
|
||||||
einops==0.8.1
|
einops==0.8.1
|
||||||
|
|
||||||
colbert-ai==0.2.21
|
|
||||||
|
|
||||||
ftfy==6.2.3
|
ftfy==6.2.3
|
||||||
pypdf==6.0.0
|
pypdf==6.0.0
|
||||||
fpdf2==2.8.2
|
fpdf2==2.8.2
|
||||||
|
|
@ -84,7 +65,7 @@ docx2txt==0.8
|
||||||
python-pptx==1.0.2
|
python-pptx==1.0.2
|
||||||
unstructured==0.16.17
|
unstructured==0.16.17
|
||||||
nltk==3.9.1
|
nltk==3.9.1
|
||||||
Markdown==3.8.2
|
Markdown==3.9
|
||||||
pypandoc==1.15
|
pypandoc==1.15
|
||||||
pandas==2.2.3
|
pandas==2.2.3
|
||||||
openpyxl==3.1.5
|
openpyxl==3.1.5
|
||||||
|
|
@ -105,7 +86,7 @@ onnxruntime==1.20.1
|
||||||
faster-whisper==1.1.1
|
faster-whisper==1.1.1
|
||||||
|
|
||||||
|
|
||||||
black==25.1.0
|
black==25.9.0
|
||||||
youtube-transcript-api==1.2.2
|
youtube-transcript-api==1.2.2
|
||||||
pytube==15.0.0
|
pytube==15.0.0
|
||||||
|
|
||||||
|
|
@ -117,11 +98,6 @@ google-api-python-client
|
||||||
google-auth-httplib2
|
google-auth-httplib2
|
||||||
google-auth-oauthlib
|
google-auth-oauthlib
|
||||||
|
|
||||||
## Tests
|
|
||||||
docker~=7.1.0
|
|
||||||
pytest~=8.4.1
|
|
||||||
pytest-docker~=3.1.1
|
|
||||||
|
|
||||||
googleapis-common-protos==1.70.0
|
googleapis-common-protos==1.70.0
|
||||||
google-cloud-storage==2.19.0
|
google-cloud-storage==2.19.0
|
||||||
|
|
||||||
|
|
@ -129,24 +105,45 @@ azure-identity==1.25.0
|
||||||
azure-storage-blob==12.24.1
|
azure-storage-blob==12.24.1
|
||||||
|
|
||||||
|
|
||||||
|
pymongo
|
||||||
|
psycopg2-binary==2.9.10
|
||||||
|
pgvector==0.4.1
|
||||||
|
|
||||||
|
PyMySQL==1.1.1
|
||||||
|
boto3==1.40.5
|
||||||
|
|
||||||
|
pymilvus==2.6.2
|
||||||
|
qdrant-client==1.14.3
|
||||||
|
playwright==1.49.1 # Caution: version must match docker-compose.playwright.yaml
|
||||||
|
elasticsearch==9.1.0
|
||||||
|
pinecone==6.0.2
|
||||||
|
oracledb==3.2.0
|
||||||
|
|
||||||
|
av==14.0.1 # Caution: Set due to FATAL FIPS SELFTEST FAILURE, see discussion https://github.com/open-webui/open-webui/discussions/15720
|
||||||
|
|
||||||
|
colbert-ai==0.2.21
|
||||||
|
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
docker~=7.1.0
|
||||||
|
pytest~=8.4.1
|
||||||
|
pytest-docker~=3.1.1
|
||||||
|
|
||||||
## LDAP
|
## LDAP
|
||||||
ldap3==2.9.1
|
ldap3==2.9.1
|
||||||
|
|
||||||
## Firecrawl
|
## Firecrawl
|
||||||
firecrawl-py==1.12.0
|
firecrawl-py==1.12.0
|
||||||
|
|
||||||
# Sougou API SDK(Tencentcloud SDK)
|
|
||||||
tencentcloud-sdk-python==3.0.1336
|
|
||||||
|
|
||||||
## Trace
|
## Trace
|
||||||
opentelemetry-api==1.36.0
|
opentelemetry-api==1.37.0
|
||||||
opentelemetry-sdk==1.36.0
|
opentelemetry-sdk==1.37.0
|
||||||
opentelemetry-exporter-otlp==1.36.0
|
opentelemetry-exporter-otlp==1.37.0
|
||||||
opentelemetry-instrumentation==0.57b0
|
opentelemetry-instrumentation==0.58b0
|
||||||
opentelemetry-instrumentation-fastapi==0.57b0
|
opentelemetry-instrumentation-fastapi==0.58b0
|
||||||
opentelemetry-instrumentation-sqlalchemy==0.57b0
|
opentelemetry-instrumentation-sqlalchemy==0.58b0
|
||||||
opentelemetry-instrumentation-redis==0.57b0
|
opentelemetry-instrumentation-redis==0.58b0
|
||||||
opentelemetry-instrumentation-requests==0.57b0
|
opentelemetry-instrumentation-requests==0.58b0
|
||||||
opentelemetry-instrumentation-logging==0.57b0
|
opentelemetry-instrumentation-logging==0.58b0
|
||||||
opentelemetry-instrumentation-httpx==0.57b0
|
opentelemetry-instrumentation-httpx==0.58b0
|
||||||
opentelemetry-instrumentation-aiohttp-client==0.57b0
|
opentelemetry-instrumentation-aiohttp-client==0.58b0
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,18 @@ if [ -n "$SPACE_ID" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PYTHON_CMD=$(command -v python3 || command -v python)
|
PYTHON_CMD=$(command -v python3 || command -v python)
|
||||||
|
UVICORN_WORKERS="${UVICORN_WORKERS:-1}"
|
||||||
|
|
||||||
WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec "$PYTHON_CMD" -m uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*' --workers "${UVICORN_WORKERS:-1}"
|
# If script is called with arguments, use them; otherwise use default workers
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
ARGS=("$@")
|
||||||
|
else
|
||||||
|
ARGS=(--workers "$UVICORN_WORKERS")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run uvicorn
|
||||||
|
WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec "$PYTHON_CMD" -m uvicorn open_webui.main:app \
|
||||||
|
--host "$HOST" \
|
||||||
|
--port "$PORT" \
|
||||||
|
--forwarded-allow-ips '*' \
|
||||||
|
"${ARGS[@]}"
|
||||||
681
package-lock.json
generated
681
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.6.32",
|
"version": "0.6.33",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.6.32",
|
"version": "0.6.33",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^4.5.0",
|
"@azure/msal-browser": "^4.5.0",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
|
|
@ -93,6 +93,8 @@
|
||||||
"turndown-plugin-gfm": "^1.0.2",
|
"turndown-plugin-gfm": "^1.0.2",
|
||||||
"undici": "^7.3.0",
|
"undici": "^7.3.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
"vega": "^6.2.0",
|
||||||
|
"vega-lite": "^6.4.1",
|
||||||
"vite-plugin-static-copy": "^2.2.0",
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"y-prosemirror": "^1.3.7",
|
"y-prosemirror": "^1.3.7",
|
||||||
"yaml": "^2.7.1",
|
"yaml": "^2.7.1",
|
||||||
|
|
@ -5592,6 +5594,99 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"strip-ansi": "^7.1.0",
|
||||||
|
"wrap-ansi": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/emoji-regex": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.2.1",
|
||||||
|
"string-width": "^7.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clone": {
|
"node_modules/clone": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||||
|
|
@ -6346,6 +6441,36 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-geo-projection": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "7",
|
||||||
|
"d3-array": "1 - 3",
|
||||||
|
"d3-geo": "1.12.0 - 3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"geo2svg": "bin/geo2svg.js",
|
||||||
|
"geograticule": "bin/geograticule.js",
|
||||||
|
"geoproject": "bin/geoproject.js",
|
||||||
|
"geoquantize": "bin/geoquantize.js",
|
||||||
|
"geostitch": "bin/geostitch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-geo-projection/node_modules/commander": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-hierarchy": {
|
"node_modules/d3-hierarchy": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
|
||||||
|
|
@ -7038,6 +7163,15 @@
|
||||||
"@esbuild/win32-x64": "0.25.1"
|
"@esbuild/win32-x64": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
|
@ -7750,6 +7884,27 @@
|
||||||
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
|
||||||
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
|
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-east-asian-width": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-func-name": {
|
"node_modules/get-func-name": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
||||||
|
|
@ -8763,6 +8918,12 @@
|
||||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/json-stringify-pretty-compact": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/json-stringify-safe": {
|
"node_modules/json-stringify-safe": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
|
|
@ -12770,6 +12931,26 @@
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/topojson-client": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"topo2geo": "bin/topo2geo",
|
||||||
|
"topomerge": "bin/topomerge",
|
||||||
|
"topoquantize": "bin/topoquantize"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/topojson-client/node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/totalist": {
|
"node_modules/totalist": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
||||||
|
|
@ -13047,6 +13228,417 @@
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vega": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega/-/vega-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-BIwalIcEGysJdQDjeVUmMWB3e50jPDNAMfLJscjEvpunU9bSt7X1OYnQxkg3uBwuRRI4nWfFZO9uIW910nLeGw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-crossfilter": "~5.1.0",
|
||||||
|
"vega-dataflow": "~6.1.0",
|
||||||
|
"vega-encode": "~5.1.0",
|
||||||
|
"vega-event-selector": "~4.0.0",
|
||||||
|
"vega-expression": "~6.1.0",
|
||||||
|
"vega-force": "~5.1.0",
|
||||||
|
"vega-format": "~2.1.0",
|
||||||
|
"vega-functions": "~6.1.0",
|
||||||
|
"vega-geo": "~5.1.0",
|
||||||
|
"vega-hierarchy": "~5.1.0",
|
||||||
|
"vega-label": "~2.1.0",
|
||||||
|
"vega-loader": "~5.1.0",
|
||||||
|
"vega-parser": "~7.1.0",
|
||||||
|
"vega-projection": "~2.1.0",
|
||||||
|
"vega-regression": "~2.1.0",
|
||||||
|
"vega-runtime": "~7.1.0",
|
||||||
|
"vega-scale": "~8.1.0",
|
||||||
|
"vega-scenegraph": "~5.1.0",
|
||||||
|
"vega-statistics": "~2.0.0",
|
||||||
|
"vega-time": "~3.1.0",
|
||||||
|
"vega-transforms": "~5.1.0",
|
||||||
|
"vega-typings": "~2.1.0",
|
||||||
|
"vega-util": "~2.1.0",
|
||||||
|
"vega-view": "~6.1.0",
|
||||||
|
"vega-view-transforms": "~5.1.0",
|
||||||
|
"vega-voronoi": "~5.1.0",
|
||||||
|
"vega-wordcloud": "~5.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-canvas": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/vega-crossfilter": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-EmVhfP3p6AM7o/lPan/QAoqjblI19BxWUlvl2TSs0xjQd8KbaYYbS4Ixt3cmEvl0QjRdBMF6CdJJ/cy9DTS4Fw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-dataflow": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-JxumGlODtFbzoQ4c/jQK8Tb/68ih0lrexlCozcMfTAwQ12XhTqCvlafh7MAKKTMBizjOfaQTHm4Jkyb1H5CfyQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-format": "^2.1.0",
|
||||||
|
"vega-loader": "^5.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-encode": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-q26oI7B+MBQYcTQcr5/c1AMsX3FvjZLQOBi7yI0vV+GEn93fElDgvhQiYrgeYSD4Exi/jBPeUXuN6p4bLz16kA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-scale": "^8.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-event-selector": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/vega-expression": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-hHgNx/fQ1Vn1u6vHSamH7lRMsOa/yQeHGGcWVmh8fZafLdwdhCM91kZD9p7+AleNpgwiwzfGogtpATFaMmDFYg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.8",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-expression/node_modules/@types/estree": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vega-force": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-force/-/vega-force-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-wdnchOSeXpF9Xx8Yp0s6Do9F7YkFeOn/E/nENtsI7NOcyHpICJ5+UkgjUo9QaQ/Yu+dIDU+sP/4NXsUtq6SMaQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-force": "^3.0.0",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-format": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-format/-/vega-format-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-i9Ht33IgqG36+S1gFDpAiKvXCPz+q+1vDhDGKK8YsgMxGOG4PzinKakI66xd7SdV4q97FgpR7odAXqtDN2wKqw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-format": "^3.1.0",
|
||||||
|
"d3-time-format": "^4.1.0",
|
||||||
|
"vega-time": "^3.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-functions": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-yooEbWt0FWMBNoohwLsl25lEh08WsWabTXbbS+q0IXZzWSpX4Cyi45+q7IFyy/2L4oaIfGIIV14dgn3srQQcGA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-color": "^3.1.0",
|
||||||
|
"d3-geo": "^3.1.1",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-expression": "^6.1.0",
|
||||||
|
"vega-scale": "^8.1.0",
|
||||||
|
"vega-scenegraph": "^5.1.0",
|
||||||
|
"vega-selections": "^6.1.0",
|
||||||
|
"vega-statistics": "^2.0.0",
|
||||||
|
"vega-time": "^3.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-geo": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-H8aBBHfthc3rzDbz/Th18+Nvp00J73q3uXGAPDQqizioDm/CoXCK8cX4pMePydBY9S6ikBiGJrLKFDa80wI20g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-color": "^3.1.0",
|
||||||
|
"d3-geo": "^3.1.1",
|
||||||
|
"vega-canvas": "^2.0.0",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-projection": "^2.1.0",
|
||||||
|
"vega-statistics": "^2.0.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-hierarchy": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-rZlU8QJNETlB6o73lGCPybZtw2fBBsRIRuFE77aCLFHdGsh6wIifhplVarqE9icBqjUHRRUOmcEYfzwVIPr65g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-hierarchy": "^3.1.2",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-label": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-label/-/vega-label-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-/hgf+zoA3FViDBehrQT42Lta3t8In6YwtMnwjYlh72zNn1p3c7E3YUBwqmAqTM1x+tudgzMRGLYig+bX1ewZxQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-canvas": "^2.0.0",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-scenegraph": "^5.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-lite": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-KO3ybHNouRK4A0al/+2fN9UqgTEfxrd/ntGLY933Hg5UOYotDVQdshR3zn7OfXwQ7uj0W96Vfa5R+QxO8am3IQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"json-stringify-pretty-compact": "~4.0.0",
|
||||||
|
"tslib": "~2.8.1",
|
||||||
|
"vega-event-selector": "~4.0.0",
|
||||||
|
"vega-expression": "~6.1.0",
|
||||||
|
"vega-util": "~2.1.0",
|
||||||
|
"yargs": "~18.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vl2pdf": "bin/vl2pdf",
|
||||||
|
"vl2png": "bin/vl2png",
|
||||||
|
"vl2svg": "bin/vl2svg",
|
||||||
|
"vl2vg": "bin/vl2vg"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://app.hubspot.com/payments/GyPC972GD9Rt"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vega": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-loader": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-GaY3BdSPbPNdtrBz8SYUBNmNd8mdPc3mtdZfdkFazQ0RD9m+Toz5oR8fKnTamNSk9fRTJX0Lp3uEqxrAlQVreg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-dsv": "^3.0.1",
|
||||||
|
"topojson-client": "^3.1.0",
|
||||||
|
"vega-format": "^2.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-parser": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-g0lrYxtmYVW8G6yXpIS4J3Uxt9OUSkc0bLu5afoYDo4rZmoOOdll3x3ebActp5LHPW+usZIE+p5nukRS2vEc7Q==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-event-selector": "^4.0.0",
|
||||||
|
"vega-functions": "^6.1.0",
|
||||||
|
"vega-scale": "^8.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-projection": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-EjRjVSoMR5ibrU7q8LaOQKP327NcOAM1+eZ+NO4ANvvAutwmbNVTmfA1VpPH+AD0AlBYc39ND/wnRk7SieDiXA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-geo": "^3.1.1",
|
||||||
|
"d3-geo-projection": "^4.0.0",
|
||||||
|
"vega-scale": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-regression": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HzC7MuoEwG1rIxRaNTqgcaYF03z/ZxYkQR2D5BN0N45kLnHY1HJXiEcZkcffTsqXdspLjn47yLi44UoCwF5fxQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-statistics": "^2.0.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-runtime": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-mItI+WHimyEcZlZrQ/zYR3LwHVeyHCWwp7MKaBjkU8EwkSxEEGVceyGUY9X2YuJLiOgkLz/6juYDbMv60pfwYA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-scale": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-VEgDuEcOec8+C8+FzLcnAmcXrv2gAJKqQifCdQhkgnsLa978vYUgVfCut/mBSMMHbH8wlUV1D0fKZTjRukA1+A==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-scale-chromatic": "^3.1.0",
|
||||||
|
"vega-time": "^3.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-scenegraph": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-4gA89CFIxkZX+4Nvl8SZF2MBOqnlj9J5zgdPh/HPx+JOwtzSlUqIhxFpFj7GWYfwzr/PyZnguBLPihPw1Og/cA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0",
|
||||||
|
"d3-shape": "^3.2.0",
|
||||||
|
"vega-canvas": "^2.0.0",
|
||||||
|
"vega-loader": "^5.1.0",
|
||||||
|
"vega-scale": "^8.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-selections": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-WaHM7D7ghHceEfMsgFeaZnDToWL0mgCFtStVOobNh/OJLh0CL7yNKeKQBqRXJv2Lx74dPNf6nj08+52ytWfW7g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "3.2.4",
|
||||||
|
"vega-expression": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-statistics": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-G93mWzPwNa6UYQRkr8Ujur9uqxbBDjDT/WpXjbDY0yygdSkRT+zXF+Sb4gjhW0nPaqdiwkn0R6kZcSPMj1bMNA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-time": "^3.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-transforms": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-mj/sO2tSuzzpiXX8JSl4DDlhEmVwM/46MTAzTNQUQzJPMI/n4ChCjr/SdEbfEyzlD4DPm1bjohZGjLc010yuMg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-statistics": "^2.0.0",
|
||||||
|
"vega-time": "^3.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-typings": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-zdis4Fg4gv37yEvTTSZEVMNhp8hwyEl7GZ4X4HHddRVRKxWFsbyKvZx/YW5Z9Ox4sjxVA2qHzEbod4Fdx+SEJA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "7946.0.16",
|
||||||
|
"vega-event-selector": "^4.0.0",
|
||||||
|
"vega-expression": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-util": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-util/-/vega-util-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-PGfp0m0QCufDmcxKJCWQy4Ov23FoF8DSXmoJwSezi3itQaa2hbxK0+xwsTMP2vy4PR16Pu25HMzgMwXVW1+33w==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/vega-view": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-view/-/vega-view-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-hmHDm/zC65lb23mb9Tr9Gx0wkxP0TMS31LpMPYxIZpvInxvUn7TYitkOtz1elr63k2YZrgmF7ztdGyQ4iCQ5fQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "^3.2.4",
|
||||||
|
"d3-timer": "^3.0.1",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-format": "^2.1.0",
|
||||||
|
"vega-functions": "^6.1.0",
|
||||||
|
"vega-runtime": "^7.1.0",
|
||||||
|
"vega-scenegraph": "^5.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-view-transforms": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-fpigh/xn/32t+An1ShoY3MLeGzNdlbAp2+HvFKzPpmpMTZqJEWkk/J/wHU7Swyc28Ta7W1z3fO+8dZkOYO5TWQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-scenegraph": "^5.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-voronoi": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-uKdsoR9x60mz7eYtVG+NhlkdQXeVdMr6jHNAHxs+W+i6kawkUp5S9jp1xf1FmW/uZvtO1eqinHQNwATcDRsiUg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-delaunay": "^6.0.4",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vega-wordcloud": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-sSdNmT8y2D7xXhM2h76dKyaYn3PA4eV49WUUkfYfqHz/vpcu10GSAoFxLhQQTkbZXR+q5ZB63tFUow9W2IFo6g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"vega-canvas": "^2.0.0",
|
||||||
|
"vega-dataflow": "^6.1.0",
|
||||||
|
"vega-scale": "^8.1.0",
|
||||||
|
"vega-statistics": "^2.0.0",
|
||||||
|
"vega-util": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/verror": {
|
"node_modules/verror": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
|
@ -14209,6 +14801,15 @@
|
||||||
"yjs": "^13.0.0"
|
"yjs": "^13.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||||
|
|
@ -14230,6 +14831,82 @@
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "18.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||||
|
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^9.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"string-width": "^7.2.0",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^22.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "22.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
|
||||||
|
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/ansi-regex": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/emoji-regex": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/string-width": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^10.3.0",
|
||||||
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yauzl": {
|
"node_modules/yauzl": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "open-webui",
|
"name": "open-webui",
|
||||||
"version": "0.6.32",
|
"version": "0.6.33",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||||
|
|
@ -137,6 +137,8 @@
|
||||||
"turndown-plugin-gfm": "^1.0.2",
|
"turndown-plugin-gfm": "^1.0.2",
|
||||||
"undici": "^7.3.0",
|
"undici": "^7.3.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
"vega": "^6.2.0",
|
||||||
|
"vega-lite": "^6.4.1",
|
||||||
"vite-plugin-static-copy": "^2.2.0",
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"y-prosemirror": "^1.3.7",
|
"y-prosemirror": "^1.3.7",
|
||||||
"yaml": "^2.7.1",
|
"yaml": "^2.7.1",
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,16 @@ authors = [
|
||||||
]
|
]
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi==0.115.7",
|
"fastapi==0.118.0",
|
||||||
"uvicorn[standard]==0.35.0",
|
"uvicorn[standard]==0.37.0",
|
||||||
"pydantic==2.11.7",
|
"pydantic==2.11.9",
|
||||||
"python-multipart==0.0.20",
|
"python-multipart==0.0.20",
|
||||||
"itsdangerous==2.2.0",
|
"itsdangerous==2.2.0",
|
||||||
|
|
||||||
"python-socketio==5.13.0",
|
"python-socketio==5.13.0",
|
||||||
"python-jose==3.4.0",
|
"python-jose==3.4.0",
|
||||||
"passlib[bcrypt]==1.7.4",
|
|
||||||
"cryptography",
|
"cryptography",
|
||||||
"bcrypt==4.3.0",
|
"bcrypt==5.0.0",
|
||||||
"argon2-cffi==25.1.0",
|
"argon2-cffi==25.1.0",
|
||||||
"PyJWT[crypto]==2.10.1",
|
"PyJWT[crypto]==2.10.1",
|
||||||
"authlib==1.6.3",
|
"authlib==1.6.3",
|
||||||
|
|
@ -76,7 +75,7 @@ dependencies = [
|
||||||
"python-pptx==1.0.2",
|
"python-pptx==1.0.2",
|
||||||
"unstructured==0.16.17",
|
"unstructured==0.16.17",
|
||||||
"nltk==3.9.1",
|
"nltk==3.9.1",
|
||||||
"Markdown==3.8.2",
|
"Markdown==3.9",
|
||||||
"pypandoc==1.15",
|
"pypandoc==1.15",
|
||||||
"pandas==2.2.3",
|
"pandas==2.2.3",
|
||||||
"openpyxl==3.1.5",
|
"openpyxl==3.1.5",
|
||||||
|
|
@ -96,8 +95,8 @@ dependencies = [
|
||||||
"onnxruntime==1.20.1",
|
"onnxruntime==1.20.1",
|
||||||
"faster-whisper==1.1.1",
|
"faster-whisper==1.1.1",
|
||||||
|
|
||||||
"black==25.1.0",
|
"black==25.9.0",
|
||||||
"youtube-transcript-api==1.1.0",
|
"youtube-transcript-api==1.2.2",
|
||||||
"pytube==15.0.0",
|
"pytube==15.0.0",
|
||||||
|
|
||||||
"pydub",
|
"pydub",
|
||||||
|
|
@ -107,8 +106,6 @@ dependencies = [
|
||||||
"google-auth-httplib2",
|
"google-auth-httplib2",
|
||||||
"google-auth-oauthlib",
|
"google-auth-oauthlib",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"googleapis-common-protos==1.70.0",
|
"googleapis-common-protos==1.70.0",
|
||||||
"google-cloud-storage==2.19.0",
|
"google-cloud-storage==2.19.0",
|
||||||
|
|
||||||
|
|
@ -116,12 +113,6 @@ dependencies = [
|
||||||
"azure-storage-blob==12.24.1",
|
"azure-storage-blob==12.24.1",
|
||||||
|
|
||||||
"ldap3==2.9.1",
|
"ldap3==2.9.1",
|
||||||
|
|
||||||
"firecrawl-py==1.12.0",
|
|
||||||
"tencentcloud-sdk-python==3.0.1336",
|
|
||||||
|
|
||||||
"oracledb>=3.2.0",
|
|
||||||
|
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">= 3.11, < 3.13.0a1"
|
requires-python = ">= 3.11, < 3.13.0a1"
|
||||||
|
|
@ -155,11 +146,14 @@ all = [
|
||||||
"elasticsearch==9.1.0",
|
"elasticsearch==9.1.0",
|
||||||
|
|
||||||
"qdrant-client==1.14.3",
|
"qdrant-client==1.14.3",
|
||||||
"pymilvus==2.5.0",
|
"pymilvus==2.6.2",
|
||||||
"pinecone==6.0.2",
|
"pinecone==6.0.2",
|
||||||
"oracledb==3.2.0",
|
"oracledb==3.2.0",
|
||||||
|
|
||||||
"colbert-ai==0.2.21",
|
"colbert-ai==0.2.21",
|
||||||
|
|
||||||
|
"firecrawl-py==1.12.0",
|
||||||
|
"tencentcloud-sdk-python==3.0.1336",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,37 @@ export const getFunctions = async (token: string = '') => {
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFunctionList = async (token: string = '') => {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
const res = await fetch(`${WEBUI_API_BASE_URL}/functions/list`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
if (!res.ok) throw await res.json();
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
return json;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
error = err.detail;
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
export const loadFunctionByUrl = async (token: string = '', url: string) => {
|
export const loadFunctionByUrl = async (token: string = '', url: string) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import Tags from './common/Tags.svelte';
|
import Tags from './common/Tags.svelte';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import XMark from '$lib/components/icons/XMark.svelte';
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
|
import Textarea from './common/Textarea.svelte';
|
||||||
|
|
||||||
export let onSubmit: Function = () => {};
|
export let onSubmit: Function = () => {};
|
||||||
export let onDelete: Function = () => {};
|
export let onDelete: Function = () => {};
|
||||||
|
|
@ -42,6 +43,8 @@
|
||||||
let enable = true;
|
let enable = true;
|
||||||
let apiVersion = '';
|
let apiVersion = '';
|
||||||
|
|
||||||
|
let headers = '';
|
||||||
|
|
||||||
let tags = [];
|
let tags = [];
|
||||||
|
|
||||||
let modelId = '';
|
let modelId = '';
|
||||||
|
|
@ -69,6 +72,22 @@
|
||||||
// remove trailing slash from url
|
// remove trailing slash from url
|
||||||
url = url.replace(/\/$/, '');
|
url = url.replace(/\/$/, '');
|
||||||
|
|
||||||
|
let _headers = null;
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
try {
|
||||||
|
_headers = JSON.parse(headers);
|
||||||
|
if (typeof _headers !== 'object' || Array.isArray(_headers)) {
|
||||||
|
_headers = null;
|
||||||
|
throw new Error('Headers must be a valid JSON object');
|
||||||
|
}
|
||||||
|
headers = JSON.stringify(_headers, null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error($i18n.t('Headers must be a valid JSON object'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const res = await verifyOpenAIConnection(
|
const res = await verifyOpenAIConnection(
|
||||||
localStorage.token,
|
localStorage.token,
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +96,8 @@
|
||||||
config: {
|
config: {
|
||||||
auth_type,
|
auth_type,
|
||||||
azure: azure,
|
azure: azure,
|
||||||
api_version: apiVersion
|
api_version: apiVersion,
|
||||||
|
...(_headers ? { headers: _headers } : {})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
direct
|
direct
|
||||||
|
|
@ -136,6 +156,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (headers) {
|
||||||
|
try {
|
||||||
|
const _headers = JSON.parse(headers);
|
||||||
|
if (typeof _headers !== 'object' || Array.isArray(_headers)) {
|
||||||
|
throw new Error('Headers must be a valid JSON object');
|
||||||
|
}
|
||||||
|
headers = JSON.stringify(_headers, null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error($i18n.t('Headers must be a valid JSON object'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// remove trailing slash from url
|
// remove trailing slash from url
|
||||||
url = url.replace(/\/$/, '');
|
url = url.replace(/\/$/, '');
|
||||||
|
|
||||||
|
|
@ -149,6 +182,7 @@
|
||||||
model_ids: modelIds,
|
model_ids: modelIds,
|
||||||
connection_type: connectionType,
|
connection_type: connectionType,
|
||||||
auth_type,
|
auth_type,
|
||||||
|
headers: headers ? JSON.parse(headers) : undefined,
|
||||||
...(!ollama && azure ? { azure: true, api_version: apiVersion } : {})
|
...(!ollama && azure ? { azure: true, api_version: apiVersion } : {})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -172,6 +206,9 @@
|
||||||
key = connection.key;
|
key = connection.key;
|
||||||
|
|
||||||
auth_type = connection.config.auth_type ?? 'bearer';
|
auth_type = connection.config.auth_type ?? 'bearer';
|
||||||
|
headers = connection.config?.headers
|
||||||
|
? JSON.stringify(connection.config.headers, null, 2)
|
||||||
|
: '';
|
||||||
|
|
||||||
enable = connection.config?.enable ?? true;
|
enable = connection.config?.enable ?? true;
|
||||||
tags = connection.config?.tags ?? [];
|
tags = connection.config?.tags ?? [];
|
||||||
|
|
@ -376,6 +413,35 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if !ollama && !direct}
|
||||||
|
<div class="flex gap-2 mt-2">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<label
|
||||||
|
for="headers-input"
|
||||||
|
class={`mb-0.5 text-xs text-gray-500
|
||||||
|
${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
|
||||||
|
>{$i18n.t('Headers')}</label
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="flex-1">
|
||||||
|
<Tooltip
|
||||||
|
content={$i18n.t(
|
||||||
|
'Enter additional headers in JSON format (e.g. {{\'{{"X-Custom-Header": "value"}}\'}})'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Textarea
|
||||||
|
className="w-full text-sm outline-hidden"
|
||||||
|
bind:value={headers}
|
||||||
|
placeholder={$i18n.t('Enter additional headers in JSON format')}
|
||||||
|
required={false}
|
||||||
|
minSize={30}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="flex gap-2 mt-2">
|
<div class="flex gap-2 mt-2">
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<label
|
<label
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,17 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === '') {
|
if (['openapi', ''].includes(type)) {
|
||||||
|
if (spec_type === 'json' && spec === '') {
|
||||||
|
toast.error($i18n.t('Please enter a valid JSON spec'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec_type === 'url' && path === '') {
|
||||||
toast.error($i18n.t('Please enter a valid path'));
|
toast.error($i18n.t('Please enter a valid path'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (direct) {
|
if (direct) {
|
||||||
const res = await getToolServerData(
|
const res = await getToolServerData(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import fileSaver from 'file-saver';
|
import fileSaver from 'file-saver';
|
||||||
const { saveAs } = fileSaver;
|
const { saveAs } = fileSaver;
|
||||||
|
|
||||||
import { WEBUI_NAME, config, functions, models, settings } from '$lib/stores';
|
import { WEBUI_NAME, config, functions as _functions, models, settings, user } from '$lib/stores';
|
||||||
import { onMount, getContext, tick } from 'svelte';
|
import { onMount, getContext, tick } from 'svelte';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
deleteFunctionById,
|
deleteFunctionById,
|
||||||
exportFunctions,
|
exportFunctions,
|
||||||
getFunctionById,
|
getFunctionById,
|
||||||
|
getFunctionList,
|
||||||
getFunctions,
|
getFunctions,
|
||||||
loadFunctionByUrl,
|
loadFunctionByUrl,
|
||||||
toggleFunctionById,
|
toggleFunctionById,
|
||||||
|
|
@ -36,6 +37,10 @@
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
import AddFunctionMenu from './Functions/AddFunctionMenu.svelte';
|
import AddFunctionMenu from './Functions/AddFunctionMenu.svelte';
|
||||||
import ImportModal from '../ImportModal.svelte';
|
import ImportModal from '../ImportModal.svelte';
|
||||||
|
import ViewSelector from '../workspace/common/ViewSelector.svelte';
|
||||||
|
import TagSelector from '../workspace/common/TagSelector.svelte';
|
||||||
|
import { capitalizeFirstLetter } from '$lib/utils';
|
||||||
|
import Spinner from '../common/Spinner.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
|
@ -44,12 +49,16 @@
|
||||||
let functionsImportInputElement: HTMLInputElement;
|
let functionsImportInputElement: HTMLInputElement;
|
||||||
let importFiles;
|
let importFiles;
|
||||||
|
|
||||||
|
let tagsContainerElement: HTMLDivElement;
|
||||||
|
let viewOption = '';
|
||||||
|
|
||||||
|
let query = '';
|
||||||
|
let selectedTag = '';
|
||||||
|
let selectedType = '';
|
||||||
|
|
||||||
let showImportModal = false;
|
let showImportModal = false;
|
||||||
|
|
||||||
let showConfirm = false;
|
let showConfirm = false;
|
||||||
let query = '';
|
|
||||||
|
|
||||||
let selectedType = 'all';
|
|
||||||
|
|
||||||
let showManifestModal = false;
|
let showManifestModal = false;
|
||||||
let showValvesModal = false;
|
let showValvesModal = false;
|
||||||
|
|
@ -57,17 +66,33 @@
|
||||||
|
|
||||||
let showDeleteConfirm = false;
|
let showDeleteConfirm = false;
|
||||||
|
|
||||||
|
let loaded = false;
|
||||||
|
let functions = null;
|
||||||
let filteredItems = [];
|
let filteredItems = [];
|
||||||
$: filteredItems = $functions
|
|
||||||
|
$: if (
|
||||||
|
functions &&
|
||||||
|
query !== undefined &&
|
||||||
|
selectedType !== undefined &&
|
||||||
|
viewOption !== undefined
|
||||||
|
) {
|
||||||
|
setFilteredItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFilteredItems = () => {
|
||||||
|
filteredItems = functions
|
||||||
.filter(
|
.filter(
|
||||||
(f) =>
|
(f) =>
|
||||||
(selectedType !== 'all' ? f.type === selectedType : true) &&
|
(selectedType !== '' ? f.type === selectedType : true) &&
|
||||||
(query === '' ||
|
(query === '' ||
|
||||||
f.name.toLowerCase().includes(query.toLowerCase()) ||
|
f.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
f.id.toLowerCase().includes(query.toLowerCase()))
|
f.id.toLowerCase().includes(query.toLowerCase())) &&
|
||||||
|
(viewOption === '' ||
|
||||||
|
(viewOption === 'created' && f.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && f.user_id !== $user?.id))
|
||||||
)
|
)
|
||||||
.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
||||||
|
};
|
||||||
const shareHandler = async (func) => {
|
const shareHandler = async (func) => {
|
||||||
const item = await getFunctionById(localStorage.token, func.id).catch((error) => {
|
const item = await getFunctionById(localStorage.token, func.id).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
|
|
@ -134,7 +159,7 @@
|
||||||
if (res) {
|
if (res) {
|
||||||
toast.success($i18n.t('Function deleted successfully'));
|
toast.success($i18n.t('Function deleted successfully'));
|
||||||
|
|
||||||
functions.set(await getFunctions(localStorage.token));
|
_functions.set(await getFunctions(localStorage.token));
|
||||||
models.set(
|
models.set(
|
||||||
await getModels(
|
await getModels(
|
||||||
localStorage.token,
|
localStorage.token,
|
||||||
|
|
@ -162,7 +187,7 @@
|
||||||
: toast.success($i18n.t('Function is now globally disabled'));
|
: toast.success($i18n.t('Function is now globally disabled'));
|
||||||
}
|
}
|
||||||
|
|
||||||
functions.set(await getFunctions(localStorage.token));
|
_functions.set(await getFunctions(localStorage.token));
|
||||||
models.set(
|
models.set(
|
||||||
await getModels(
|
await getModels(
|
||||||
localStorage.token,
|
localStorage.token,
|
||||||
|
|
@ -174,7 +199,16 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
|
viewOption = localStorage?.workspaceViewOption || '';
|
||||||
|
functions = await getFunctionList(localStorage.token).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
loaded = true;
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
if (event.key === 'Shift') {
|
if (event.key === 'Shift') {
|
||||||
shiftKey = true;
|
shiftKey = true;
|
||||||
|
|
@ -222,16 +256,95 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col mt-1.5 mb-0.5 px-[16px]">
|
{#if loaded}
|
||||||
<div class="flex justify-between items-center mb-1">
|
<div class="px-4.5 w-full">
|
||||||
<div class="flex md:self-center text-xl items-center font-medium px-0.5">
|
<div class="flex flex-col gap-1 px-1 mt-2.5 mb-2">
|
||||||
|
<div class="flex justify-between items-center mb-1 w-full">
|
||||||
|
<input
|
||||||
|
id="documents-import-input"
|
||||||
|
bind:this={functionsImportInputElement}
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
console.log(importFiles);
|
||||||
|
showConfirm = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
|
<div>
|
||||||
{$i18n.t('Functions')}
|
{$i18n.t('Functions')}
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
</div>
|
||||||
<span class="text-base font-lg text-gray-500 dark:text-gray-300">{filteredItems.length}</span>
|
|
||||||
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
|
{filteredItems.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full space-x-2">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
{#if $user?.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={() => {
|
||||||
|
functionsImportInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Import')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if functions.length}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={async () => {
|
||||||
|
const _functions = await exportFunctions(localStorage.token).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_functions) {
|
||||||
|
let blob = new Blob([JSON.stringify(_functions)], {
|
||||||
|
type: 'application/json'
|
||||||
|
});
|
||||||
|
saveAs(blob, `functions-export-${Date.now()}.json`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Export')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<AddFunctionMenu
|
||||||
|
createHandler={() => {
|
||||||
|
goto('/admin/functions/create');
|
||||||
|
}}
|
||||||
|
importFromLinkHandler={() => {
|
||||||
|
showImportModal = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Function')}</div>
|
||||||
|
</div>
|
||||||
|
</AddFunctionMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
|
|
@ -255,69 +368,43 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
<AddFunctionMenu
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
||||||
createHandler={() => {
|
on:wheel={(e) => {
|
||||||
goto('/admin/functions/create');
|
if (e.deltaY !== 0) {
|
||||||
}}
|
e.preventDefault();
|
||||||
importFromLinkHandler={() => {
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
showImportModal = true;
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap"
|
||||||
|
bind:this={tagsContainerElement}
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<ViewSelector
|
||||||
</div>
|
bind:value={viewOption}
|
||||||
</AddFunctionMenu>
|
onChange={async (value) => {
|
||||||
|
localStorage.workspaceViewOption = value;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TagSelector
|
||||||
|
bind:value={selectedType}
|
||||||
|
items={[
|
||||||
|
{ value: 'pipe', label: $i18n.t('Pipe') },
|
||||||
|
{ value: 'filter', label: $i18n.t('Filter') },
|
||||||
|
{ value: 'action', label: $i18n.t('Action') }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full">
|
{#if (filteredItems ?? []).length !== 0}
|
||||||
<div
|
<div class="px-3 my-2 gap-1 lg:gap-2 grid lg:grid-cols-2">
|
||||||
class="flex gap-1 scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium rounded-full bg-transparent"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="min-w-fit p-1.5 {selectedType === 'all'
|
|
||||||
? ''
|
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition"
|
|
||||||
on:click={() => {
|
|
||||||
selectedType = 'all';
|
|
||||||
}}>{$i18n.t('All')}</button
|
|
||||||
>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="min-w-fit p-1.5 {selectedType === 'pipe'
|
|
||||||
? ''
|
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition"
|
|
||||||
on:click={() => {
|
|
||||||
selectedType = 'pipe';
|
|
||||||
}}>{$i18n.t('Pipe')}</button
|
|
||||||
>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="min-w-fit p-1.5 {selectedType === 'filter'
|
|
||||||
? ''
|
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition"
|
|
||||||
on:click={() => {
|
|
||||||
selectedType = 'filter';
|
|
||||||
}}>{$i18n.t('Filter')}</button
|
|
||||||
>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="min-w-fit p-1.5 {selectedType === 'action'
|
|
||||||
? ''
|
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition"
|
|
||||||
on:click={() => {
|
|
||||||
selectedType = 'action';
|
|
||||||
}}>{$i18n.t('Action')}</button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-5 px-[16px]">
|
|
||||||
{#each filteredItems as func (func.id)}
|
{#each filteredItems as func (func.id)}
|
||||||
<div
|
<div
|
||||||
class=" flex space-x-4 cursor-pointer w-full px-2 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
class=" flex space-x-4 cursor-pointer w-full px-2 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
|
||||||
|
|
@ -328,29 +415,39 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center text-left">
|
<div class="flex items-center text-left">
|
||||||
<div class=" flex-1 self-center pl-1">
|
<div class=" flex-1 self-center pl-1">
|
||||||
<div class=" font-semibold flex items-center gap-1.5">
|
<Tooltip content={func.id} placement="top-start">
|
||||||
|
<div class=" flex items-center gap-1.5">
|
||||||
<div
|
<div
|
||||||
class=" text-xs font-semibold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
|
class=" text-xs font-semibold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
|
||||||
>
|
>
|
||||||
{func.type}
|
{func.type}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="line-clamp-1 text-sm">
|
||||||
|
{func.name}
|
||||||
|
</div>
|
||||||
{#if func?.meta?.manifest?.version}
|
{#if func?.meta?.manifest?.version}
|
||||||
<div
|
<div class=" text-gray-500 text-xs font-medium shrink-0">
|
||||||
class="text-xs font-semibold px-1 rounded-sm line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
v{func?.meta?.manifest?.version ?? ''}
|
v{func?.meta?.manifest?.version ?? ''}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class=" line-clamp-1">
|
|
||||||
{func.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<div class="flex gap-1.5 px-1">
|
<div class="flex gap-1.5 px-1">
|
||||||
<div class=" text-gray-500 text-xs font-medium shrink-0">{func.id}</div>
|
<div class="text-xs text-gray-500 shrink-0">
|
||||||
|
<Tooltip
|
||||||
|
content={func?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
|
className="flex shrink-0"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
{$i18n.t('By {{name}}', {
|
||||||
|
name: capitalizeFirstLetter(
|
||||||
|
func?.user?.name ?? func?.user?.email ?? $i18n.t('Deleted User')
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
||||||
{func.meta.description}
|
{func.meta.description}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -475,6 +572,18 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
|
<div class="max-w-md text-center">
|
||||||
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No functions found')}</div>
|
||||||
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class=" text-gray-500 text-xs mt-1 mb-2">
|
<!-- <div class=" text-gray-500 text-xs mt-1 mb-2">
|
||||||
ⓘ {$i18n.t(
|
ⓘ {$i18n.t(
|
||||||
|
|
@ -482,87 +591,8 @@
|
||||||
)}
|
)}
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class=" flex justify-end w-full mb-2 px-[16px]">
|
|
||||||
<div class="flex space-x-2">
|
|
||||||
<input
|
|
||||||
id="documents-import-input"
|
|
||||||
bind:this={functionsImportInputElement}
|
|
||||||
bind:files={importFiles}
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
hidden
|
|
||||||
on:change={() => {
|
|
||||||
console.log(importFiles);
|
|
||||||
showConfirm = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={() => {
|
|
||||||
functionsImportInputElement.click();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Functions')}</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if $functions.length}
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={async () => {
|
|
||||||
const _functions = await exportFunctions(localStorage.token).catch((error) => {
|
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_functions) {
|
|
||||||
let blob = new Blob([JSON.stringify(_functions)], {
|
|
||||||
type: 'application/json'
|
|
||||||
});
|
|
||||||
saveAs(blob, `functions-export-${Date.now()}.json`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
|
||||||
{$i18n.t('Export Functions')} ({$functions.length})
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if $config?.features.enable_community_sharing}
|
{#if $config?.features.enable_community_sharing}
|
||||||
<div class=" my-16 px-[16px]">
|
<div class=" my-16">
|
||||||
<div class=" text-xl font-medium mb-1 line-clamp-1">
|
<div class=" text-xl font-medium mb-1 line-clamp-1">
|
||||||
{$i18n.t('Made by Open WebUI Community')}
|
{$i18n.t('Made by Open WebUI Community')}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -587,6 +617,7 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
bind:show={showDeleteConfirm}
|
bind:show={showDeleteConfirm}
|
||||||
|
|
@ -670,3 +701,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
{:else}
|
||||||
|
<div class="w-full h-full flex justify-center items-center">
|
||||||
|
<Spinner className="size-5" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -41,27 +41,27 @@
|
||||||
|
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
class="w-full max-w-[190px] text-sm rounded-xl p-1 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
|
class="w-full max-w-[190px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||||
sideOffset={-2}
|
sideOffset={6}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="start"
|
||||||
transition={flyAndScale}
|
transition={flyAndScale}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
createHandler();
|
createHandler();
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-2">
|
<div class=" self-center mr-2">
|
||||||
<PencilSolid />
|
<Pencil />
|
||||||
</div>
|
</div>
|
||||||
<div class=" self-center truncate">{$i18n.t('New Function')}</div>
|
<div class=" self-center truncate">{$i18n.t('New Function')}</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
importFromLinkHandler();
|
importFromLinkHandler();
|
||||||
show = false;
|
show = false;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import type { i18n as i18nType } from 'i18next';
|
import type { i18n as i18nType } from 'i18next';
|
||||||
|
import Textarea from '$lib/components/common/Textarea.svelte';
|
||||||
|
|
||||||
const i18n = getContext<Writable<i18nType>>('i18n');
|
const i18n = getContext<Writable<i18nType>>('i18n');
|
||||||
|
|
||||||
|
|
@ -31,6 +32,7 @@
|
||||||
let TTS_ENGINE = '';
|
let TTS_ENGINE = '';
|
||||||
let TTS_MODEL = '';
|
let TTS_MODEL = '';
|
||||||
let TTS_VOICE = '';
|
let TTS_VOICE = '';
|
||||||
|
let TTS_OPENAI_PARAMS = '';
|
||||||
let TTS_SPLIT_ON: TTS_RESPONSE_SPLIT = TTS_RESPONSE_SPLIT.PUNCTUATION;
|
let TTS_SPLIT_ON: TTS_RESPONSE_SPLIT = TTS_RESPONSE_SPLIT.PUNCTUATION;
|
||||||
let TTS_AZURE_SPEECH_REGION = '';
|
let TTS_AZURE_SPEECH_REGION = '';
|
||||||
let TTS_AZURE_SPEECH_BASE_URL = '';
|
let TTS_AZURE_SPEECH_BASE_URL = '';
|
||||||
|
|
@ -98,18 +100,28 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateConfigHandler = async () => {
|
const updateConfigHandler = async () => {
|
||||||
|
let openaiParams = {};
|
||||||
|
try {
|
||||||
|
openaiParams = TTS_OPENAI_PARAMS ? JSON.parse(TTS_OPENAI_PARAMS) : {};
|
||||||
|
TTS_OPENAI_PARAMS = JSON.stringify(openaiParams, null, 2);
|
||||||
|
} catch (e) {
|
||||||
|
toast.error($i18n.t('Invalid JSON format for Parameters'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const res = await updateAudioConfig(localStorage.token, {
|
const res = await updateAudioConfig(localStorage.token, {
|
||||||
tts: {
|
tts: {
|
||||||
OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
|
OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
|
||||||
OPENAI_API_KEY: TTS_OPENAI_API_KEY,
|
OPENAI_API_KEY: TTS_OPENAI_API_KEY,
|
||||||
|
OPENAI_PARAMS: openaiParams,
|
||||||
API_KEY: TTS_API_KEY,
|
API_KEY: TTS_API_KEY,
|
||||||
ENGINE: TTS_ENGINE,
|
ENGINE: TTS_ENGINE,
|
||||||
MODEL: TTS_MODEL,
|
MODEL: TTS_MODEL,
|
||||||
VOICE: TTS_VOICE,
|
VOICE: TTS_VOICE,
|
||||||
SPLIT_ON: TTS_SPLIT_ON,
|
|
||||||
AZURE_SPEECH_REGION: TTS_AZURE_SPEECH_REGION,
|
AZURE_SPEECH_REGION: TTS_AZURE_SPEECH_REGION,
|
||||||
AZURE_SPEECH_BASE_URL: TTS_AZURE_SPEECH_BASE_URL,
|
AZURE_SPEECH_BASE_URL: TTS_AZURE_SPEECH_BASE_URL,
|
||||||
AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT
|
AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT,
|
||||||
|
SPLIT_ON: TTS_SPLIT_ON
|
||||||
},
|
},
|
||||||
stt: {
|
stt: {
|
||||||
OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
|
OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
|
||||||
|
|
@ -146,6 +158,7 @@
|
||||||
console.log(res);
|
console.log(res);
|
||||||
TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
|
TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
|
||||||
TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
|
TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
|
||||||
|
TTS_OPENAI_PARAMS = JSON.stringify(res?.tts?.OPENAI_PARAMS ?? '', null, 2);
|
||||||
TTS_API_KEY = res.tts.API_KEY;
|
TTS_API_KEY = res.tts.API_KEY;
|
||||||
|
|
||||||
TTS_ENGINE = res.tts.ENGINE;
|
TTS_ENGINE = res.tts.ENGINE;
|
||||||
|
|
@ -612,6 +625,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Additional Parameters')}</div>
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<Textarea
|
||||||
|
className="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||||
|
bind:value={TTS_OPENAI_PARAMS}
|
||||||
|
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||||
|
minSize={100}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if TTS_ENGINE === 'elevenlabs'}
|
{:else if TTS_ENGINE === 'elevenlabs'}
|
||||||
<div class=" flex gap-2">
|
<div class=" flex gap-2">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
|
|
|
||||||
|
|
@ -714,6 +714,21 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex justify-between w-full mt-2">
|
||||||
|
<div class="self-center text-xs font-medium">
|
||||||
|
<Tooltip content={''} placement="top-start">
|
||||||
|
{$i18n.t('Parameters')}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<Textarea
|
||||||
|
bind:value={RAGConfig.DOCLING_PARAMETERS}
|
||||||
|
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
||||||
|
minSize={100}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
|
{:else if RAGConfig.CONTENT_EXTRACTION_ENGINE === 'document_intelligence'}
|
||||||
<div class="my-0.5 flex gap-2 pr-2">
|
<div class="my-0.5 flex gap-2 pr-2">
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,13 @@
|
||||||
|
|
||||||
let updateModelId = null;
|
let updateModelId = null;
|
||||||
let updateProgress = null;
|
let updateProgress = null;
|
||||||
|
let updateModelsControllers = {};
|
||||||
|
let updateCancelled = false;
|
||||||
let showExperimentalOllama = false;
|
let showExperimentalOllama = false;
|
||||||
|
|
||||||
const MAX_PARALLEL_DOWNLOADS = 3;
|
const MAX_PARALLEL_DOWNLOADS = 3;
|
||||||
|
|
||||||
let modelTransferring = false;
|
let modelLoading = false;
|
||||||
let modelTag = '';
|
let modelTag = '';
|
||||||
|
|
||||||
let createModelLoading = false;
|
let createModelLoading = false;
|
||||||
|
|
@ -65,17 +67,31 @@
|
||||||
let deleteModelTag = '';
|
let deleteModelTag = '';
|
||||||
|
|
||||||
const updateModelsHandler = async () => {
|
const updateModelsHandler = async () => {
|
||||||
|
updateCancelled = false;
|
||||||
|
toast.info('Checking for model updates...');
|
||||||
|
|
||||||
for (const model of ollamaModels) {
|
for (const model of ollamaModels) {
|
||||||
|
if (updateCancelled) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
console.debug(model);
|
console.debug(model);
|
||||||
|
|
||||||
updateModelId = model.id;
|
updateModelId = model.id;
|
||||||
const [res, controller] = await pullModel(localStorage.token, model.id, urlIdx).catch(
|
const [res, controller] = await pullModel(localStorage.token, model.id, urlIdx).catch(
|
||||||
(error) => {
|
(error) => {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
}
|
||||||
|
return [null, null];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
updateModelsControllers = {
|
||||||
|
...updateModelsControllers,
|
||||||
|
[model.id]: controller
|
||||||
|
};
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
const reader = res.body
|
const reader = res.body
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
|
@ -108,19 +124,28 @@
|
||||||
} else {
|
} else {
|
||||||
updateProgress = 100;
|
updateProgress = 100;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
toast.success(data.status);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.name !== 'AbortError') {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete updateModelsControllers[model.id];
|
||||||
|
updateModelsControllers = { ...updateModelsControllers };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateCancelled) {
|
||||||
|
toast.info('Model update cancelled');
|
||||||
|
} else {
|
||||||
|
toast.success('All models are up to date');
|
||||||
|
}
|
||||||
updateModelId = null;
|
updateModelId = null;
|
||||||
updateProgress = null;
|
updateProgress = null;
|
||||||
};
|
};
|
||||||
|
|
@ -143,10 +168,13 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modelLoading = true;
|
||||||
const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, urlIdx).catch(
|
const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, urlIdx).catch(
|
||||||
(error) => {
|
(error) => {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
}
|
||||||
|
return [null, null];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -202,8 +230,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.success(data.status);
|
|
||||||
|
|
||||||
MODEL_DOWNLOAD_POOL.set({
|
MODEL_DOWNLOAD_POOL.set({
|
||||||
...$MODEL_DOWNLOAD_POOL,
|
...$MODEL_DOWNLOAD_POOL,
|
||||||
[sanitizedModelTag]: {
|
[sanitizedModelTag]: {
|
||||||
|
|
@ -216,6 +242,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.name !== 'AbortError') {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
if (typeof err !== 'string') {
|
if (typeof err !== 'string') {
|
||||||
err = err.message;
|
err = err.message;
|
||||||
|
|
@ -223,12 +250,15 @@
|
||||||
|
|
||||||
toast.error(`${err}`);
|
toast.error(`${err}`);
|
||||||
// opts.callback({ success: false, error, modelName: opts.modelName });
|
// opts.callback({ success: false, error, modelName: opts.modelName });
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
|
console.log($MODEL_DOWNLOAD_POOL[sanitizedModelTag]);
|
||||||
|
|
||||||
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
|
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]?.done) {
|
||||||
toast.success(
|
toast.success(
|
||||||
$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
|
$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
|
||||||
modelName: sanitizedModelTag
|
modelName: sanitizedModelTag
|
||||||
|
|
@ -253,11 +283,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
modelTag = '';
|
modelTag = '';
|
||||||
modelTransferring = false;
|
modelLoading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadModelHandler = async () => {
|
const uploadModelHandler = async () => {
|
||||||
modelTransferring = true;
|
modelLoading = true;
|
||||||
|
|
||||||
let uploaded = false;
|
let uploaded = false;
|
||||||
let fileResponse = null;
|
let fileResponse = null;
|
||||||
|
|
@ -396,7 +426,7 @@
|
||||||
modelUploadInputElement.value = '';
|
modelUploadInputElement.value = '';
|
||||||
}
|
}
|
||||||
modelInputFile = null;
|
modelInputFile = null;
|
||||||
modelTransferring = false;
|
modelLoading = false;
|
||||||
uploadProgress = null;
|
uploadProgress = null;
|
||||||
|
|
||||||
models.set(
|
models.set(
|
||||||
|
|
@ -425,6 +455,14 @@
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cancelUpdateModelHandler = async (model: string) => {
|
||||||
|
const controller = updateModelsControllers[model];
|
||||||
|
if (controller) {
|
||||||
|
controller.abort();
|
||||||
|
updateCancelled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const cancelModelPullHandler = async (model: string) => {
|
const cancelModelPullHandler = async (model: string) => {
|
||||||
const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
|
const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model];
|
||||||
if (abortController) {
|
if (abortController) {
|
||||||
|
|
@ -605,14 +643,15 @@
|
||||||
bind:value={modelTag}
|
bind:value={modelTag}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('Pull Model')} placement="top">
|
||||||
<button
|
<button
|
||||||
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
pullModelHandler();
|
pullModelHandler();
|
||||||
}}
|
}}
|
||||||
disabled={modelTransferring}
|
disabled={modelLoading || modelTag.trim() === ''}
|
||||||
>
|
>
|
||||||
{#if modelTransferring}
|
{#if modelLoading}
|
||||||
<div class="self-center">
|
<div class="self-center">
|
||||||
<svg
|
<svg
|
||||||
class=" w-4 h-4"
|
class=" w-4 h-4"
|
||||||
|
|
@ -658,6 +697,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
|
@ -670,8 +710,35 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if updateModelId}
|
{#if updateModelId}
|
||||||
<div class="text-xs">
|
<div class="text-xs flex justify-between items-center">
|
||||||
Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
|
<div>Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}</div>
|
||||||
|
|
||||||
|
<Tooltip content={$i18n.t('Cancel')}>
|
||||||
|
<button
|
||||||
|
class="text-gray-800 dark:text-gray-100"
|
||||||
|
on:click={() => {
|
||||||
|
cancelUpdateModelHandler(updateModelId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-4 h-4 text-gray-800 dark:text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18 17.94 6M18 18 6.06 6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
@ -754,11 +821,13 @@
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip content={$i18n.t('Delete Model')} placement="top">
|
||||||
<button
|
<button
|
||||||
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showModelDeleteConfirm = true;
|
showModelDeleteConfirm = true;
|
||||||
}}
|
}}
|
||||||
|
disabled={deleteModelTag === ''}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -773,6 +842,7 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -799,12 +869,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex self-start">
|
<div class="flex self-start">
|
||||||
|
<Tooltip content={$i18n.t('Create Model')} placement="top">
|
||||||
<button
|
<button
|
||||||
class="px-2.5 py-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
|
class="px-2.5 py-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition disabled:cursor-not-allowed"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
createModelHandler();
|
createModelHandler();
|
||||||
}}
|
}}
|
||||||
disabled={createModelLoading}
|
disabled={createModelLoading ||
|
||||||
|
createModelName.trim() === '' ||
|
||||||
|
createModelObject.trim() === ''}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -820,6 +893,7 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -936,12 +1010,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
|
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
|
||||||
|
<Tooltip content={$i18n.t('Upload Model')} placement="top">
|
||||||
<button
|
<button
|
||||||
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
|
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg disabled:cursor-not-allowed transition"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={modelTransferring}
|
disabled={modelLoading}
|
||||||
>
|
>
|
||||||
{#if modelTransferring}
|
{#if modelLoading}
|
||||||
<div class="self-center">
|
<div class="self-center">
|
||||||
<svg
|
<svg
|
||||||
class=" w-4 h-4"
|
class=" w-4 h-4"
|
||||||
|
|
@ -987,6 +1062,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1.5">
|
<div class="flex flex-col gap-1">
|
||||||
{#each servers as server, idx}
|
{#each servers as server, idx}
|
||||||
<Connection
|
<Connection
|
||||||
bind:connection={server}
|
bind:connection={server}
|
||||||
|
|
|
||||||
|
|
@ -56,14 +56,7 @@
|
||||||
let showDefaultPermissionsModal = false;
|
let showDefaultPermissionsModal = false;
|
||||||
|
|
||||||
const setGroups = async () => {
|
const setGroups = async () => {
|
||||||
const allGroups = await getGroups(localStorage.token);
|
groups = await getGroups(localStorage.token);
|
||||||
const userGroup = allGroups.find((g) => g.name.toLowerCase() === 'user');
|
|
||||||
|
|
||||||
if (userGroup) {
|
|
||||||
defaultPermissions = userGroup.permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups = allGroups.filter((g) => g.name.toLowerCase() !== 'user');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addGroupHandler = async (group) => {
|
const addGroupHandler = async (group) => {
|
||||||
|
|
@ -110,6 +103,7 @@
|
||||||
total = res.total;
|
total = res.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultPermissions = await getUserDefaultPermissions(localStorage.token);
|
||||||
await setGroups();
|
await setGroups();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,11 @@
|
||||||
<div class=" px-5 pt-3 pb-5 w-full">
|
<div class=" px-5 pt-3 pb-5 w-full">
|
||||||
<div class="flex self-center w-full">
|
<div class="flex self-center w-full">
|
||||||
<div class=" self-start h-full mr-6">
|
<div class=" self-start h-full mr-6">
|
||||||
<UserProfileImage bind:profileImageUrl={_user.profile_image_url} user={_user} />
|
<UserProfileImage
|
||||||
|
imageClassName="size-14"
|
||||||
|
bind:profileImageUrl={_user.profile_image_url}
|
||||||
|
user={_user}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex-1">
|
<div class=" flex-1">
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = async () => {
|
const onChange = async () => {
|
||||||
$socket?.emit('channel-events', {
|
$socket?.emit('events:channel', {
|
||||||
channel_id: id,
|
channel_id: id,
|
||||||
message_id: null,
|
message_id: null,
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -180,7 +180,7 @@
|
||||||
chatId.set('');
|
chatId.set('');
|
||||||
}
|
}
|
||||||
|
|
||||||
$socket?.on('channel-events', channelEventHandler);
|
$socket?.on('events:channel', channelEventHandler);
|
||||||
|
|
||||||
mediaQuery = window.matchMedia('(min-width: 1024px)');
|
mediaQuery = window.matchMedia('(min-width: 1024px)');
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$socket?.off('channel-events', channelEventHandler);
|
$socket?.off('events:channel', channelEventHandler);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -876,12 +876,12 @@
|
||||||
richText={$settings?.richTextInput ?? true}
|
richText={$settings?.richTextInput ?? true}
|
||||||
showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
|
showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
|
||||||
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
||||||
(!$mobile ||
|
!$mobile &&
|
||||||
!(
|
!(
|
||||||
'ontouchstart' in window ||
|
'ontouchstart' in window ||
|
||||||
navigator.maxTouchPoints > 0 ||
|
navigator.maxTouchPoints > 0 ||
|
||||||
navigator.msMaxTouchPoints > 0
|
navigator.msMaxTouchPoints > 0
|
||||||
))}
|
)}
|
||||||
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
||||||
floatingMenuPlacement={'top-start'}
|
floatingMenuPlacement={'top-start'}
|
||||||
{suggestions}
|
{suggestions}
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = async () => {
|
const onChange = async () => {
|
||||||
$socket?.emit('channel-events', {
|
$socket?.emit('events:channel', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
message_id: threadId,
|
message_id: threadId,
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -156,11 +156,11 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
$socket?.on('channel-events', channelEventHandler);
|
$socket?.on('events:channel', channelEventHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$socket?.off('channel-events', channelEventHandler);
|
$socket?.off('events:channel', channelEventHandler);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@
|
||||||
import Sidebar from '../icons/Sidebar.svelte';
|
import Sidebar from '../icons/Sidebar.svelte';
|
||||||
import { getFunctions } from '$lib/apis/functions';
|
import { getFunctions } from '$lib/apis/functions';
|
||||||
import Image from '../common/Image.svelte';
|
import Image from '../common/Image.svelte';
|
||||||
|
import { updateFolderById } from '$lib/apis/folders';
|
||||||
|
|
||||||
export let chatIdProp = '';
|
export let chatIdProp = '';
|
||||||
|
|
||||||
|
|
@ -219,10 +220,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveSessionSelectedModels = () => {
|
const saveSessionSelectedModels = () => {
|
||||||
if (selectedModels.length === 0 || (selectedModels.length === 1 && selectedModels[0] === '')) {
|
const selectedModelsString = JSON.stringify(selectedModels);
|
||||||
|
if (
|
||||||
|
selectedModels.length === 0 ||
|
||||||
|
(selectedModels.length === 1 && selectedModels[0] === '') ||
|
||||||
|
sessionStorage.selectedModels === selectedModelsString
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sessionStorage.selectedModels = JSON.stringify(selectedModels);
|
sessionStorage.selectedModels = selectedModelsString;
|
||||||
console.log('saveSessionSelectedModels', selectedModels, sessionStorage.selectedModels);
|
console.log('saveSessionSelectedModels', selectedModels, sessionStorage.selectedModels);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -294,7 +300,7 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showMessage = async (message) => {
|
const showMessage = async (message, ignoreSettings = false) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||||
|
|
@ -320,7 +326,7 @@
|
||||||
await tick();
|
await tick();
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if ($settings?.scrollOnBranchChange ?? true) {
|
if (($settings?.scrollOnBranchChange ?? true) || ignoreSettings) {
|
||||||
const messageElement = document.getElementById(`message-${message.id}`);
|
const messageElement = document.getElementById(`message-${message.id}`);
|
||||||
if (messageElement) {
|
if (messageElement) {
|
||||||
messageElement.scrollIntoView({ behavior: 'smooth' });
|
messageElement.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
|
@ -499,12 +505,33 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const savedModelIds = async () => {
|
||||||
|
if (
|
||||||
|
$selectedFolder &&
|
||||||
|
selectedModels.filter((modelId) => modelId !== '').length > 0 &&
|
||||||
|
JSON.stringify($selectedFolder?.data?.model_ids) !== JSON.stringify(selectedModels)
|
||||||
|
) {
|
||||||
|
const res = await updateFolderById(localStorage.token, $selectedFolder.id, {
|
||||||
|
data: {
|
||||||
|
model_ids: selectedModels
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (selectedModels !== null) {
|
||||||
|
savedModelIds();
|
||||||
|
}
|
||||||
|
|
||||||
let pageSubscribe = null;
|
let pageSubscribe = null;
|
||||||
|
let showControlsSubscribe = null;
|
||||||
|
let selectedFolderSubscribe = null;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
console.log('mounted');
|
console.log('mounted');
|
||||||
window.addEventListener('message', onMessageHandler);
|
window.addEventListener('message', onMessageHandler);
|
||||||
$socket?.on('chat-events', chatEventHandler);
|
$socket?.on('events', chatEventHandler);
|
||||||
|
|
||||||
pageSubscribe = page.subscribe(async (p) => {
|
pageSubscribe = page.subscribe(async (p) => {
|
||||||
if (p.url.pathname === '/') {
|
if (p.url.pathname === '/') {
|
||||||
|
|
@ -548,7 +575,7 @@
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
showControls.subscribe(async (value) => {
|
showControlsSubscribe = showControls.subscribe(async (value) => {
|
||||||
if (controlPane && !$mobile) {
|
if (controlPane && !$mobile) {
|
||||||
try {
|
try {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
@ -569,17 +596,32 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
selectedFolderSubscribe = selectedFolder.subscribe(async (folder) => {
|
||||||
|
if (
|
||||||
|
folder?.data?.model_ids &&
|
||||||
|
JSON.stringify(selectedModels) !== JSON.stringify(folder.data.model_ids)
|
||||||
|
) {
|
||||||
|
selectedModels = folder.data.model_ids;
|
||||||
|
|
||||||
|
console.log('Set selectedModels from folder data:', selectedModels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const chatInput = document.getElementById('chat-input');
|
const chatInput = document.getElementById('chat-input');
|
||||||
chatInput?.focus();
|
chatInput?.focus();
|
||||||
|
|
||||||
chats.subscribe(() => {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
try {
|
||||||
pageSubscribe();
|
pageSubscribe();
|
||||||
|
showControlsSubscribe();
|
||||||
|
selectedFolderSubscribe();
|
||||||
chatIdUnsubscriber?.();
|
chatIdUnsubscriber?.();
|
||||||
window.removeEventListener('message', onMessageHandler);
|
window.removeEventListener('message', onMessageHandler);
|
||||||
$socket?.off('chat-events', chatEventHandler);
|
$socket?.off('events', chatEventHandler);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// File upload functions
|
// File upload functions
|
||||||
|
|
@ -780,6 +822,7 @@
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
const initNewChat = async () => {
|
const initNewChat = async () => {
|
||||||
|
console.log('initNewChat');
|
||||||
if ($user?.role !== 'admin' && $user?.permissions?.chat?.temporary_enforced) {
|
if ($user?.role !== 'admin' && $user?.permissions?.chat?.temporary_enforced) {
|
||||||
await temporaryChatEnabled.set(true);
|
await temporaryChatEnabled.set(true);
|
||||||
}
|
}
|
||||||
|
|
@ -829,6 +872,9 @@
|
||||||
selectedModels = selectedModels.filter((modelId) =>
|
selectedModels = selectedModels.filter((modelId) =>
|
||||||
$models.map((m) => m.id).includes(modelId)
|
$models.map((m) => m.id).includes(modelId)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if ($selectedFolder?.data?.model_ids) {
|
||||||
|
selectedModels = $selectedFolder?.data?.model_ids;
|
||||||
} else {
|
} else {
|
||||||
if (sessionStorage.selectedModels) {
|
if (sessionStorage.selectedModels) {
|
||||||
selectedModels = JSON.parse(sessionStorage.selectedModels);
|
selectedModels = JSON.parse(sessionStorage.selectedModels);
|
||||||
|
|
@ -841,6 +887,8 @@
|
||||||
selectedModels = $config?.default_models.split(',');
|
selectedModels = $config?.default_models.split(',');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
selectedModels = selectedModels.filter((modelId) => availableModels.includes(modelId));
|
selectedModels = selectedModels.filter((modelId) => availableModels.includes(modelId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1499,7 +1547,7 @@
|
||||||
|
|
||||||
chatFiles.push(
|
chatFiles.push(
|
||||||
..._files.filter((item) =>
|
..._files.filter((item) =>
|
||||||
['doc', 'text', 'file', 'note', 'chat', 'collection'].includes(item.type)
|
['doc', 'text', 'file', 'note', 'chat', 'folder', 'collection'].includes(item.type)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
chatFiles = chatFiles.filter(
|
chatFiles = chatFiles.filter(
|
||||||
|
|
@ -2159,8 +2207,8 @@
|
||||||
|
|
||||||
selectedFolder.set(null);
|
selectedFolder.set(null);
|
||||||
} else {
|
} else {
|
||||||
_chatId = 'local';
|
_chatId = `local:${$socket?.id}`; // Use socket id for temporary chat
|
||||||
await chatId.set('local');
|
await chatId.set(_chatId);
|
||||||
}
|
}
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,9 @@
|
||||||
showEmbeds
|
showEmbeds
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
|
|
||||||
import Modal from '../common/Modal.svelte';
|
|
||||||
import Controls from './Controls/Controls.svelte';
|
import Controls from './Controls/Controls.svelte';
|
||||||
import CallOverlay from './MessageInput/CallOverlay.svelte';
|
import CallOverlay from './MessageInput/CallOverlay.svelte';
|
||||||
import Drawer from '../common/Drawer.svelte';
|
import Drawer from '../common/Drawer.svelte';
|
||||||
import Overview from './Overview.svelte';
|
|
||||||
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
|
|
||||||
import Artifacts from './Artifacts.svelte';
|
import Artifacts from './Artifacts.svelte';
|
||||||
import Embeds from './ChatControls/Embeds.svelte';
|
import Embeds from './ChatControls/Embeds.svelte';
|
||||||
|
|
||||||
|
|
@ -154,7 +151,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlowProvider>
|
|
||||||
{#if !largeScreen}
|
{#if !largeScreen}
|
||||||
{#if $showControls}
|
{#if $showControls}
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
@ -189,15 +185,18 @@
|
||||||
{:else if $showArtifacts}
|
{:else if $showArtifacts}
|
||||||
<Artifacts {history} />
|
<Artifacts {history} />
|
||||||
{:else if $showOverview}
|
{:else if $showOverview}
|
||||||
|
{#await import('./Overview.svelte') then { default: Overview }}
|
||||||
<Overview
|
<Overview
|
||||||
{history}
|
{history}
|
||||||
on:nodeclick={(e) => {
|
onNodeClick={(e) => {
|
||||||
showMessage(e.detail.node.data.message);
|
const node = e.node;
|
||||||
|
showMessage(node.data.message, true);
|
||||||
}}
|
}}
|
||||||
on:close={() => {
|
onClose={() => {
|
||||||
showControls.set(false);
|
showControls.set(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/await}
|
||||||
{:else}
|
{:else}
|
||||||
<Controls
|
<Controls
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
|
|
@ -276,21 +275,24 @@
|
||||||
{:else if $showArtifacts}
|
{:else if $showArtifacts}
|
||||||
<Artifacts {history} overlay={dragged} />
|
<Artifacts {history} overlay={dragged} />
|
||||||
{:else if $showOverview}
|
{:else if $showOverview}
|
||||||
|
{#await import('./Overview.svelte') then { default: Overview }}
|
||||||
<Overview
|
<Overview
|
||||||
{history}
|
{history}
|
||||||
on:nodeclick={(e) => {
|
onNodeClick={(e) => {
|
||||||
if (e.detail.node.data.message.favorite) {
|
const node = e.node;
|
||||||
history.messages[e.detail.node.data.message.id].favorite = true;
|
if (node?.data?.message?.favorite) {
|
||||||
|
history.messages[node.data.message.id].favorite = true;
|
||||||
} else {
|
} else {
|
||||||
history.messages[e.detail.node.data.message.id].favorite = null;
|
history.messages[node.data.message.id].favorite = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage(e.detail.node.data.message);
|
showMessage(node.data.message, true);
|
||||||
}}
|
}}
|
||||||
on:close={() => {
|
onClose={() => {
|
||||||
showControls.set(false);
|
showControls.set(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/await}
|
||||||
{:else}
|
{:else}
|
||||||
<Controls
|
<Controls
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
|
|
@ -306,4 +308,3 @@
|
||||||
{/if}
|
{/if}
|
||||||
</Pane>
|
</Pane>
|
||||||
{/if}
|
{/if}
|
||||||
</SvelteFlowProvider>
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,14 @@
|
||||||
class="pointer-events-auto z-20 flex justify-between items-center py-3 px-2 font-primar text-gray-900 dark:text-white"
|
class="pointer-events-auto z-20 flex justify-between items-center py-3 px-2 font-primar text-gray-900 dark:text-white"
|
||||||
>
|
>
|
||||||
<div class="flex-1 flex items-center justify-between pl-2">
|
<div class="flex-1 flex items-center justify-between pl-2">
|
||||||
<div class="flex items-center space-x-2">
|
<a
|
||||||
|
class="flex items-center space-x-2 hover:underline"
|
||||||
|
href={$embed?.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{$embed?.title ?? 'Embedded Content'}
|
{$embed?.title ?? 'Embedded Content'}
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
import LightBulb from '$lib/components/icons/LightBulb.svelte';
|
import LightBulb from '$lib/components/icons/LightBulb.svelte';
|
||||||
import Markdown from '../Messages/Markdown.svelte';
|
import Markdown from '../Messages/Markdown.svelte';
|
||||||
import Skeleton from '../Messages/Skeleton.svelte';
|
import Skeleton from '../Messages/Skeleton.svelte';
|
||||||
|
import { chatId, models, socket } from '$lib/stores';
|
||||||
|
|
||||||
export let id = '';
|
export let id = '';
|
||||||
export let messageId = '';
|
export let messageId = '';
|
||||||
|
|
@ -118,6 +119,9 @@
|
||||||
let res;
|
let res;
|
||||||
[res, controller] = await chatCompletion(localStorage.token, {
|
[res, controller] = await chatCompletion(localStorage.token, {
|
||||||
model: model,
|
model: model,
|
||||||
|
model_item: $models.find((m) => m.id === model),
|
||||||
|
session_id: $socket?.id,
|
||||||
|
chat_id: $chatId,
|
||||||
messages: [
|
messages: [
|
||||||
...messages,
|
...messages,
|
||||||
{
|
{
|
||||||
|
|
@ -246,11 +250,11 @@
|
||||||
{#if responseContent === null}
|
{#if responseContent === null}
|
||||||
{#if !floatingInput}
|
{#if !floatingInput}
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-0.5 shrink-0 p-1 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-lg shadow-xl"
|
class="flex flex-row shrink-0 p-0.5 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-xl shadow-xl border border-gray-100 dark:border-gray-800"
|
||||||
>
|
>
|
||||||
{#each actions as action}
|
{#each actions as action}
|
||||||
<button
|
<button
|
||||||
class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-sm flex items-center gap-1 min-w-fit"
|
class="px-1.5 py-[1px] hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl flex items-center gap-1 min-w-fit transition"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
selectedText = window.getSelection().toString();
|
selectedText = window.getSelection().toString();
|
||||||
selectedAction = action;
|
selectedAction = action;
|
||||||
|
|
@ -280,7 +284,7 @@
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="py-1 flex dark:text-gray-100 bg-gray-50 dark:bg-gray-800 border border-gray-100 dark:border-gray-850 w-72 rounded-full shadow-xl"
|
class="py-1 flex dark:text-gray-100 bg-white dark:bg-gray-850 border border-gray-100 dark:border-gray-800 w-72 rounded-full shadow-xl"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -295,7 +299,7 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="ml-1 mr-2">
|
<div class="ml-1 mr-1">
|
||||||
<button
|
<button
|
||||||
class="{floatingInputValue !== ''
|
class="{floatingInputValue !== ''
|
||||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||||
|
|
@ -321,19 +325,22 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="bg-white dark:bg-gray-850 dark:text-gray-100 rounded-xl shadow-xl w-80 max-w-full">
|
|
||||||
<div
|
<div
|
||||||
class="bg-gray-50/50 dark:bg-gray-800 dark:text-gray-100 text-medium rounded-xl px-3.5 py-3 w-full"
|
class="bg-white dark:bg-gray-850 dark:text-gray-100 rounded-3xl shadow-xl w-80 max-w-full border border-gray-100 dark:border-gray-800"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-3xl px-3.5 pt-3 w-full"
|
||||||
>
|
>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
<Markdown id={`${id}-float-prompt`} {content} />
|
<Markdown id={`${id}-float-prompt`} {content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-4xl w-full">
|
||||||
<div
|
<div
|
||||||
class="bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-xl px-3.5 py-3 w-full"
|
class=" max-h-80 overflow-y-auto w-full markdown-prose-xs px-3.5 py-3"
|
||||||
|
id="response-container"
|
||||||
>
|
>
|
||||||
<div class=" max-h-80 overflow-y-auto w-full markdown-prose-xs" id="response-container">
|
|
||||||
{#if !responseContent || responseContent?.trim() === ''}
|
{#if !responseContent || responseContent?.trim() === ''}
|
||||||
<Skeleton size="sm" />
|
<Skeleton size="sm" />
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
||||||
|
|
@ -885,8 +885,6 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(suggestions);
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
|
@ -1201,12 +1199,12 @@
|
||||||
floatingMenuPlacement={'top-start'}
|
floatingMenuPlacement={'top-start'}
|
||||||
insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false}
|
insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false}
|
||||||
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
||||||
(!$mobile ||
|
!$mobile &&
|
||||||
!(
|
!(
|
||||||
'ontouchstart' in window ||
|
'ontouchstart' in window ||
|
||||||
navigator.maxTouchPoints > 0 ||
|
navigator.maxTouchPoints > 0 ||
|
||||||
navigator.msMaxTouchPoints > 0
|
navigator.msMaxTouchPoints > 0
|
||||||
))}
|
)}
|
||||||
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
|
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
|
||||||
largeTextAsFile={($settings?.largeTextAsFile ?? false) && !shiftKey}
|
largeTextAsFile={($settings?.largeTextAsFile ?? false) && !shiftKey}
|
||||||
autocomplete={$config?.features?.enable_autocomplete_generation &&
|
autocomplete={$config?.features?.enable_autocomplete_generation &&
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
import Database from '$lib/components/icons/Database.svelte';
|
import Database from '$lib/components/icons/Database.svelte';
|
||||||
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
|
import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte';
|
||||||
import Youtube from '$lib/components/icons/Youtube.svelte';
|
import Youtube from '$lib/components/icons/Youtube.svelte';
|
||||||
|
import { folders } from '$lib/stores';
|
||||||
|
import Folder from '$lib/components/icons/Folder.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
|
@ -144,14 +146,25 @@
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
items = [...collections, ...collection_files, ...legacy_collections, ...legacy_documents].map(
|
let folder_items = $folders.map((folder) => ({
|
||||||
(item) => {
|
...folder,
|
||||||
|
type: 'folder',
|
||||||
|
description: $i18n.t('Folder'),
|
||||||
|
title: folder.name
|
||||||
|
}));
|
||||||
|
|
||||||
|
items = [
|
||||||
|
...folder_items,
|
||||||
|
...collections,
|
||||||
|
...collection_files,
|
||||||
|
...legacy_collections,
|
||||||
|
...legacy_documents
|
||||||
|
].map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {})
|
...(item?.legacy || item?.meta?.legacy || item?.meta?.document ? { legacy: true } : {})
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
fuse = new Fuse(items, {
|
fuse = new Fuse(items, {
|
||||||
keys: ['name', 'description']
|
keys: ['name', 'description']
|
||||||
|
|
@ -213,6 +226,8 @@
|
||||||
>
|
>
|
||||||
{#if item?.type === 'collection'}
|
{#if item?.type === 'collection'}
|
||||||
<Database className="size-4" />
|
<Database className="size-4" />
|
||||||
|
{:else if item?.type === 'folder'}
|
||||||
|
<Folder className="size-4" />
|
||||||
{:else}
|
{:else}
|
||||||
<DocumentPage className="size-4" />
|
<DocumentPage className="size-4" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@
|
||||||
{#each Object.keys(tools) as toolId}
|
{#each Object.keys(tools) as toolId}
|
||||||
<button
|
<button
|
||||||
class="relative flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
class="relative flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||||
on:click={(e) => {
|
on:click={async (e) => {
|
||||||
if (!(tools[toolId]?.authenticated ?? true)) {
|
if (!(tools[toolId]?.authenticated ?? true)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|
@ -338,9 +338,18 @@
|
||||||
let serverId = parts?.at(-1) ?? toolId;
|
let serverId = parts?.at(-1) ?? toolId;
|
||||||
|
|
||||||
const authUrl = getOAuthClientAuthorizationUrl(serverId, 'mcp');
|
const authUrl = getOAuthClientAuthorizationUrl(serverId, 'mcp');
|
||||||
window.open(authUrl, '_blank', 'noopener');
|
window.open(authUrl, '_self', 'noopener');
|
||||||
} else {
|
} else {
|
||||||
tools[toolId].enabled = !tools[toolId].enabled;
|
tools[toolId].enabled = !tools[toolId].enabled;
|
||||||
|
|
||||||
|
const state = tools[toolId].enabled;
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
selectedToolIds = [...selectedToolIds, toolId];
|
||||||
|
} else {
|
||||||
|
selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -383,18 +392,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class=" shrink-0">
|
<div class=" shrink-0">
|
||||||
<Switch
|
<Switch state={tools[toolId].enabled} />
|
||||||
state={tools[toolId].enabled}
|
|
||||||
on:change={async (e) => {
|
|
||||||
const state = e.detail;
|
|
||||||
await tick();
|
|
||||||
if (state) {
|
|
||||||
selectedToolIds = [...selectedToolIds, toolId];
|
|
||||||
} else {
|
|
||||||
selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
export let id = '';
|
export let id = '';
|
||||||
export let sources = [];
|
export let sources = [];
|
||||||
|
export let readOnly = false;
|
||||||
|
|
||||||
let citations = [];
|
let citations = [];
|
||||||
let showPercentage = false;
|
let showPercentage = false;
|
||||||
|
|
@ -26,12 +27,18 @@
|
||||||
if (citations[sourceIdx]?.source?.embed_url) {
|
if (citations[sourceIdx]?.source?.embed_url) {
|
||||||
const embedUrl = citations[sourceIdx].source.embed_url;
|
const embedUrl = citations[sourceIdx].source.embed_url;
|
||||||
if (embedUrl) {
|
if (embedUrl) {
|
||||||
|
if (readOnly) {
|
||||||
|
// Open in new tab if readOnly
|
||||||
|
window.open(embedUrl, '_blank');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
showControls.set(true);
|
showControls.set(true);
|
||||||
showEmbeds.set(true);
|
showEmbeds.set(true);
|
||||||
embed.set({
|
embed.set({
|
||||||
title: citations[sourceIdx]?.source?.name || 'Embedded Content',
|
title: citations[sourceIdx]?.source?.name || 'Embedded Content',
|
||||||
url: embedUrl
|
url: embedUrl
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedCitation = citations[sourceIdx];
|
selectedCitation = citations[sourceIdx];
|
||||||
showCitationModal = true;
|
showCitationModal = true;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
||||||
import { executeCode } from '$lib/apis/utils';
|
import { executeCode } from '$lib/apis/utils';
|
||||||
import { copyToClipboard, renderMermaidDiagram } from '$lib/utils';
|
import { copyToClipboard, renderMermaidDiagram, renderVegaVisualization } from '$lib/utils';
|
||||||
|
|
||||||
import 'highlight.js/styles/github-dark.min.css';
|
import 'highlight.js/styles/github-dark.min.css';
|
||||||
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
let _token = null;
|
let _token = null;
|
||||||
|
|
||||||
let mermaidHtml = null;
|
let mermaidHtml = null;
|
||||||
|
let vegaHtml = null;
|
||||||
|
|
||||||
let highlightedCode = null;
|
let highlightedCode = null;
|
||||||
let executing = false;
|
let executing = false;
|
||||||
|
|
@ -325,7 +326,26 @@
|
||||||
const render = async () => {
|
const render = async () => {
|
||||||
onUpdate(token);
|
onUpdate(token);
|
||||||
if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
|
if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
|
||||||
|
try {
|
||||||
mermaidHtml = await renderMermaidDiagram(code);
|
mermaidHtml = await renderMermaidDiagram(code);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to render mermaid diagram:', error);
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`);
|
||||||
|
mermaidHtml = null;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
(lang === 'vega' || lang === 'vega-lite') &&
|
||||||
|
(token?.raw ?? '').slice(-4).includes('```')
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
vegaHtml = await renderVegaVisualization(code);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to render Vega visualization:', error);
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error($i18n.t('Failed to render diagram') + `: ${errorMsg}`);
|
||||||
|
vegaHtml = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -397,6 +417,16 @@
|
||||||
{:else}
|
{:else}
|
||||||
<pre class="mermaid">{code}</pre>
|
<pre class="mermaid">{code}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if lang === 'vega' || lang === 'vega-lite'}
|
||||||
|
{#if vegaHtml}
|
||||||
|
<SvgPanZoom
|
||||||
|
className="rounded-3xl max-h-fit overflow-hidden"
|
||||||
|
svg={vegaHtml}
|
||||||
|
content={_token.text}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<pre class="vega">{code}</pre>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<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-2.5 pr-3 text-text-300 pl-4.5 text-xs font-medium dark:text-white"
|
||||||
|
|
|
||||||
|
|
@ -576,8 +576,6 @@
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
if (buttonsContainerElement) {
|
if (buttonsContainerElement) {
|
||||||
console.log(buttonsContainerElement);
|
|
||||||
|
|
||||||
buttonsContainerElement.addEventListener('wheel', function (event) {
|
buttonsContainerElement.addEventListener('wheel', function (event) {
|
||||||
if (buttonsContainerElement.scrollWidth <= buttonsContainerElement.clientWidth) {
|
if (buttonsContainerElement.scrollWidth <= buttonsContainerElement.clientWidth) {
|
||||||
// If the container is not scrollable, horizontal scroll
|
// If the container is not scrollable, horizontal scroll
|
||||||
|
|
@ -811,6 +809,7 @@
|
||||||
bind:this={citationsElement}
|
bind:this={citationsElement}
|
||||||
id={message?.id}
|
id={message?.id}
|
||||||
sources={message?.sources ?? message?.citations}
|
sources={message?.sources ?? message?.citations}
|
||||||
|
{readOnly}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
class="w-full max-w-[200px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-850 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
|
class="w-full max-w-[200px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
|
||||||
sideOffset={-2}
|
sideOffset={-2}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="start"
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
|
{#if history.length > 1}
|
||||||
<div class="pt-3 px-1">
|
<div class="pt-3 px-1">
|
||||||
<span class="relative flex size-1.5 rounded-full justify-center items-center">
|
<span class="relative flex size-1.5 rounded-full justify-center items-center">
|
||||||
{#if status?.done === false}
|
{#if status?.done === false}
|
||||||
|
|
@ -75,10 +76,12 @@
|
||||||
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-gray-500 dark:bg-gray-300 opacity-75"
|
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-gray-500 dark:bg-gray-300 opacity-75"
|
||||||
></span>
|
></span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="relative inline-flex size-1.5 rounded-full bg-gray-500 dark:bg-gray-300"
|
<span
|
||||||
|
class="relative inline-flex size-1.5 rounded-full bg-gray-500 dark:bg-gray-300"
|
||||||
></span>
|
></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<StatusItem {status} />
|
<StatusItem {status} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (selectedModels.length > 0 && $models.length > 0) {
|
$: if (selectedModels.length > 0 && $models.length > 0) {
|
||||||
selectedModels = selectedModels.map((model) =>
|
const _selectedModels = selectedModels.map((model) =>
|
||||||
$models.map((m) => m.id).includes(model) ? model : ''
|
$models.map((m) => m.id).includes(model) ? model : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (JSON.stringify(_selectedModels) !== JSON.stringify(selectedModels)) {
|
||||||
|
selectedModels = _selectedModels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,26 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateFuse = () => {
|
||||||
|
if (fuse) {
|
||||||
|
fuse.setCollection(
|
||||||
|
items.map((item) => {
|
||||||
|
const _item = {
|
||||||
|
...item,
|
||||||
|
modelName: item.model?.name,
|
||||||
|
tags: (item.model?.tags ?? []).map((tag) => tag.name).join(' '),
|
||||||
|
desc: item.model?.info?.meta?.description
|
||||||
|
};
|
||||||
|
return _item;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (items) {
|
||||||
|
updateFuse();
|
||||||
|
}
|
||||||
|
|
||||||
$: filteredItems = (
|
$: filteredItems = (
|
||||||
searchValue
|
searchValue
|
||||||
? fuse
|
? fuse
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $temporaryChatEnabled && $chatId === 'local'}
|
{#if $temporaryChatEnabled && ($chatId ?? '').startsWith('local:')}
|
||||||
<div class=" w-full z-30 text-center">
|
<div class=" w-full z-30 text-center">
|
||||||
<div class="text-xs text-gray-500">{$i18n.t('Temporary Chat')}</div>
|
<div class="text-xs text-gray-500">{$i18n.t('Temporary Chat')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,199 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, createEventDispatcher, onDestroy } from 'svelte';
|
import { getContext, createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
|
import { useSvelteFlow, useNodesInitialized, useStore, SvelteFlowProvider } from '@xyflow/svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const i18n = getContext('i18n');
|
|
||||||
|
|
||||||
import { onMount, tick } from 'svelte';
|
import View from './Overview/View.svelte';
|
||||||
|
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import { models, showOverview, theme, user } from '$lib/stores';
|
|
||||||
|
|
||||||
import '@xyflow/svelte/dist/style.css';
|
|
||||||
|
|
||||||
import CustomNode from './Overview/Node.svelte';
|
|
||||||
import Flow from './Overview/Flow.svelte';
|
|
||||||
import XMark from '../icons/XMark.svelte';
|
|
||||||
import ArrowLeft from '../icons/ArrowLeft.svelte';
|
|
||||||
|
|
||||||
const { width, height } = useStore();
|
|
||||||
|
|
||||||
const { fitView, getViewport } = useSvelteFlow();
|
|
||||||
const nodesInitialized = useNodesInitialized();
|
|
||||||
|
|
||||||
export let history;
|
export let history;
|
||||||
|
|
||||||
let selectedMessageId = null;
|
export let onClose;
|
||||||
|
export let onNodeClick;
|
||||||
const nodes = writable([]);
|
|
||||||
const edges = writable([]);
|
|
||||||
|
|
||||||
const nodeTypes = {
|
|
||||||
custom: CustomNode
|
|
||||||
};
|
|
||||||
|
|
||||||
$: if (history) {
|
|
||||||
drawFlow();
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (history && history.currentId) {
|
|
||||||
focusNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
const focusNode = async () => {
|
|
||||||
if (selectedMessageId === null) {
|
|
||||||
await fitView({ nodes: [{ id: history.currentId }] });
|
|
||||||
} else {
|
|
||||||
await fitView({ nodes: [{ id: selectedMessageId }] });
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedMessageId = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawFlow = async () => {
|
|
||||||
const nodeList = [];
|
|
||||||
const edgeList = [];
|
|
||||||
const levelOffset = 150; // Vertical spacing between layers
|
|
||||||
const siblingOffset = 250; // Horizontal spacing between nodes at the same layer
|
|
||||||
|
|
||||||
// Map to keep track of node positions at each level
|
|
||||||
let positionMap = new Map();
|
|
||||||
|
|
||||||
// Helper function to truncate labels
|
|
||||||
function createLabel(content) {
|
|
||||||
const maxLength = 100;
|
|
||||||
return content.length > maxLength ? content.substr(0, maxLength) + '...' : content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create nodes and map children to ensure alignment in width
|
|
||||||
let layerWidths = {}; // Track widths of each layer
|
|
||||||
|
|
||||||
Object.keys(history.messages).forEach((id) => {
|
|
||||||
const message = history.messages[id];
|
|
||||||
const level = message.parentId ? (positionMap.get(message.parentId)?.level ?? -1) + 1 : 0;
|
|
||||||
if (!layerWidths[level]) layerWidths[level] = 0;
|
|
||||||
|
|
||||||
positionMap.set(id, {
|
|
||||||
id: message.id,
|
|
||||||
level,
|
|
||||||
position: layerWidths[level]++
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Adjust positions based on siblings count to centralize vertical spacing
|
|
||||||
Object.keys(history.messages).forEach((id) => {
|
|
||||||
const pos = positionMap.get(id);
|
|
||||||
const xOffset = pos.position * siblingOffset;
|
|
||||||
const y = pos.level * levelOffset;
|
|
||||||
const x = xOffset;
|
|
||||||
|
|
||||||
nodeList.push({
|
|
||||||
id: pos.id,
|
|
||||||
type: 'custom',
|
|
||||||
data: {
|
|
||||||
user: $user,
|
|
||||||
message: history.messages[id],
|
|
||||||
model: $models.find((model) => model.id === history.messages[id].model)
|
|
||||||
},
|
|
||||||
position: { x, y }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create edges
|
|
||||||
const parentId = history.messages[id].parentId;
|
|
||||||
if (parentId) {
|
|
||||||
edgeList.push({
|
|
||||||
id: parentId + '-' + pos.id,
|
|
||||||
source: parentId,
|
|
||||||
target: pos.id,
|
|
||||||
selectable: false,
|
|
||||||
class: ' dark:fill-gray-300 fill-gray-300',
|
|
||||||
type: 'smoothstep',
|
|
||||||
animated: history.currentId === id || recurseCheckChild(id, history.currentId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await edges.set([...edgeList]);
|
|
||||||
await nodes.set([...nodeList]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const recurseCheckChild = (nodeId, currentId) => {
|
|
||||||
const node = history.messages[nodeId];
|
|
||||||
return (
|
|
||||||
node.childrenIds &&
|
|
||||||
node.childrenIds.some((id) => id === currentId || recurseCheckChild(id, currentId))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
drawFlow();
|
|
||||||
|
|
||||||
nodesInitialized.subscribe(async (initialized) => {
|
|
||||||
if (initialized) {
|
|
||||||
await tick();
|
|
||||||
const res = await fitView({ nodes: [{ id: history.currentId }] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
width.subscribe((value) => {
|
|
||||||
if (value) {
|
|
||||||
// fitView();
|
|
||||||
fitView({ nodes: [{ id: history.currentId }] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
height.subscribe((value) => {
|
|
||||||
if (value) {
|
|
||||||
// fitView();
|
|
||||||
fitView({ nodes: [{ id: history.currentId }] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
console.log('Overview destroyed');
|
|
||||||
|
|
||||||
nodes.set([]);
|
|
||||||
edges.set([]);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full relative">
|
<SvelteFlowProvider>
|
||||||
<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-4 py-3">
|
<View {history} {onClose} {onNodeClick} />
|
||||||
<div class="flex items-center gap-2.5">
|
</SvelteFlowProvider>
|
||||||
<button
|
|
||||||
class="self-center p-0.5"
|
|
||||||
on:click={() => {
|
|
||||||
showOverview.set(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowLeft className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="self-center p-0.5"
|
|
||||||
on:click={() => {
|
|
||||||
dispatch('close');
|
|
||||||
showOverview.set(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<XMark className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if $nodes.length > 0}
|
|
||||||
<Flow
|
|
||||||
{nodes}
|
|
||||||
{nodeTypes}
|
|
||||||
{edges}
|
|
||||||
on:nodeclick={(e) => {
|
|
||||||
console.log(e.detail.node.data);
|
|
||||||
dispatch('nodeclick', e.detail);
|
|
||||||
selectedMessageId = e.detail.node.data.message.id;
|
|
||||||
fitView({ nodes: [{ id: selectedMessageId }] });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,22 @@
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import { theme } from '$lib/stores';
|
import { theme } from '$lib/stores';
|
||||||
import { Background, Controls, SvelteFlow, BackgroundVariant } from '@xyflow/svelte';
|
import {
|
||||||
|
Background,
|
||||||
|
Controls,
|
||||||
|
SvelteFlow,
|
||||||
|
BackgroundVariant,
|
||||||
|
ControlButton
|
||||||
|
} from '@xyflow/svelte';
|
||||||
|
import BarsArrowUp from '$lib/components/icons/BarsArrowUp.svelte';
|
||||||
|
import Bars3BottomLeft from '$lib/components/icons/Bars3BottomLeft.svelte';
|
||||||
|
import AlignVertical from '$lib/components/icons/AlignVertical.svelte';
|
||||||
|
import AlignHorizontal from '$lib/components/icons/AlignHorizontal.svelte';
|
||||||
|
|
||||||
export let nodes;
|
export let nodes;
|
||||||
export let nodeTypes;
|
export let nodeTypes;
|
||||||
export let edges;
|
export let edges;
|
||||||
|
export let setLayoutDirection;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SvelteFlow
|
<SvelteFlow
|
||||||
|
|
@ -31,6 +42,13 @@
|
||||||
console.log('Flow initialized');
|
console.log('Flow initialized');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Controls showLock={false} />
|
<Controls showLock={false}>
|
||||||
|
<ControlButton on:click={() => setLayoutDirection('vertical')} title="Vertical Layout">
|
||||||
|
<AlignVertical className="size-4" />
|
||||||
|
</ControlButton>
|
||||||
|
<ControlButton on:click={() => setLayoutDirection('horizontal')} title="Horizontal Layout">
|
||||||
|
<AlignHorizontal className="size-4" />
|
||||||
|
</ControlButton>
|
||||||
|
</Controls>
|
||||||
<Background variant={BackgroundVariant.Dots} />
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
</SvelteFlow>
|
</SvelteFlow>
|
||||||
|
|
|
||||||
207
src/lib/components/chat/Overview/View.svelte
Normal file
207
src/lib/components/chat/Overview/View.svelte
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { getContext, createEventDispatcher, onDestroy } from 'svelte';
|
||||||
|
import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
import { onMount, tick } from 'svelte';
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { models, showOverview, theme, user } from '$lib/stores';
|
||||||
|
|
||||||
|
import '@xyflow/svelte/dist/style.css';
|
||||||
|
|
||||||
|
import CustomNode from './Node.svelte';
|
||||||
|
import Flow from './Flow.svelte';
|
||||||
|
import XMark from '../../icons/XMark.svelte';
|
||||||
|
import ArrowLeft from '../../icons/ArrowLeft.svelte';
|
||||||
|
|
||||||
|
const { width, height } = useStore();
|
||||||
|
|
||||||
|
const { fitView, getViewport } = useSvelteFlow();
|
||||||
|
const nodesInitialized = useNodesInitialized();
|
||||||
|
|
||||||
|
export let history;
|
||||||
|
export let onClose;
|
||||||
|
export let onNodeClick;
|
||||||
|
|
||||||
|
let selectedMessageId = null;
|
||||||
|
|
||||||
|
const nodes = writable([]);
|
||||||
|
const edges = writable([]);
|
||||||
|
|
||||||
|
let layoutDirection = 'vertical';
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
custom: CustomNode
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (history) {
|
||||||
|
drawFlow(layoutDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (history && history.currentId) {
|
||||||
|
focusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusNode = async () => {
|
||||||
|
if (selectedMessageId === null) {
|
||||||
|
await fitView({ nodes: [{ id: history.currentId }] });
|
||||||
|
} else {
|
||||||
|
await fitView({ nodes: [{ id: selectedMessageId }] });
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedMessageId = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawFlow = async (direction) => {
|
||||||
|
const nodeList = [];
|
||||||
|
const edgeList = [];
|
||||||
|
const levelOffset = direction === 'vertical' ? 150 : 300;
|
||||||
|
const siblingOffset = direction === 'vertical' ? 250 : 150;
|
||||||
|
|
||||||
|
// Map to keep track of node positions at each level
|
||||||
|
let positionMap = new Map();
|
||||||
|
|
||||||
|
// Helper function to truncate labels
|
||||||
|
function createLabel(content) {
|
||||||
|
const maxLength = 100;
|
||||||
|
return content.length > maxLength ? content.substr(0, maxLength) + '...' : content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create nodes and map children to ensure alignment in width
|
||||||
|
let layerWidths = {}; // Track widths of each layer
|
||||||
|
|
||||||
|
Object.keys(history.messages).forEach((id) => {
|
||||||
|
const message = history.messages[id];
|
||||||
|
const level = message.parentId ? (positionMap.get(message.parentId)?.level ?? -1) + 1 : 0;
|
||||||
|
if (!layerWidths[level]) layerWidths[level] = 0;
|
||||||
|
|
||||||
|
positionMap.set(id, {
|
||||||
|
id: message.id,
|
||||||
|
level,
|
||||||
|
position: layerWidths[level]++
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adjust positions based on siblings count to centralize vertical spacing
|
||||||
|
Object.keys(history.messages).forEach((id) => {
|
||||||
|
const pos = positionMap.get(id);
|
||||||
|
const x = direction === 'vertical' ? pos.position * siblingOffset : pos.level * levelOffset;
|
||||||
|
const y = direction === 'vertical' ? pos.level * levelOffset : pos.position * siblingOffset;
|
||||||
|
|
||||||
|
nodeList.push({
|
||||||
|
id: pos.id,
|
||||||
|
type: 'custom',
|
||||||
|
data: {
|
||||||
|
user: $user,
|
||||||
|
message: history.messages[id],
|
||||||
|
model: $models.find((model) => model.id === history.messages[id].model)
|
||||||
|
},
|
||||||
|
position: { x, y }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create edges
|
||||||
|
const parentId = history.messages[id].parentId;
|
||||||
|
if (parentId) {
|
||||||
|
edgeList.push({
|
||||||
|
id: parentId + '-' + pos.id,
|
||||||
|
source: parentId,
|
||||||
|
target: pos.id,
|
||||||
|
selectable: false,
|
||||||
|
class: ' dark:fill-gray-300 fill-gray-300',
|
||||||
|
type: 'smoothstep',
|
||||||
|
animated: history.currentId === id || recurseCheckChild(id, history.currentId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await edges.set([...edgeList]);
|
||||||
|
await nodes.set([...nodeList]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const recurseCheckChild = (nodeId, currentId) => {
|
||||||
|
const node = history.messages[nodeId];
|
||||||
|
return (
|
||||||
|
node.childrenIds &&
|
||||||
|
node.childrenIds.some((id) => id === currentId || recurseCheckChild(id, currentId))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLayoutDirection = (direction) => {
|
||||||
|
layoutDirection = direction;
|
||||||
|
drawFlow(layoutDirection);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
drawFlow(layoutDirection);
|
||||||
|
|
||||||
|
nodesInitialized.subscribe(async (initialized) => {
|
||||||
|
if (initialized) {
|
||||||
|
await tick();
|
||||||
|
const res = await fitView({ nodes: [{ id: history.currentId }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
width.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
// fitView();
|
||||||
|
fitView({ nodes: [{ id: history.currentId }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
height.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
// fitView();
|
||||||
|
fitView({ nodes: [{ id: history.currentId }] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
console.log('Overview destroyed');
|
||||||
|
|
||||||
|
nodes.set([]);
|
||||||
|
edges.set([]);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full h-full relative">
|
||||||
|
<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-4 py-3">
|
||||||
|
<div class="flex items-center gap-2.5">
|
||||||
|
<button
|
||||||
|
class="self-center p-0.5"
|
||||||
|
on:click={() => {
|
||||||
|
showOverview.set(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="self-center p-0.5"
|
||||||
|
on:click={() => {
|
||||||
|
onClose();
|
||||||
|
showOverview.set(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<XMark className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $nodes.length > 0}
|
||||||
|
<Flow
|
||||||
|
{nodes}
|
||||||
|
{nodeTypes}
|
||||||
|
{edges}
|
||||||
|
{setLayoutDirection}
|
||||||
|
on:nodeclick={(e) => {
|
||||||
|
onNodeClick(e.detail);
|
||||||
|
selectedMessageId = e.detail.node.data.message.id;
|
||||||
|
fitView({ nodes: [{ id: selectedMessageId }] });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
import { getChatList } from '$lib/apis/chats';
|
||||||
|
import { updateFolderById } from '$lib/apis/folders';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
config,
|
config,
|
||||||
user,
|
user,
|
||||||
|
|
@ -25,7 +28,6 @@
|
||||||
import MessageInput from './MessageInput.svelte';
|
import MessageInput from './MessageInput.svelte';
|
||||||
import FolderPlaceholder from './Placeholder/FolderPlaceholder.svelte';
|
import FolderPlaceholder from './Placeholder/FolderPlaceholder.svelte';
|
||||||
import FolderTitle from './Placeholder/FolderTitle.svelte';
|
import FolderTitle from './Placeholder/FolderTitle.svelte';
|
||||||
import { getChatList } from '$lib/apis/chats';
|
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
|
@ -58,7 +60,6 @@
|
||||||
export let toolServers = [];
|
export let toolServers = [];
|
||||||
|
|
||||||
let models = [];
|
let models = [];
|
||||||
|
|
||||||
let selectedModelIdx = 0;
|
let selectedModelIdx = 0;
|
||||||
|
|
||||||
$: if (selectedModels.length > 0) {
|
$: if (selectedModels.length > 0) {
|
||||||
|
|
@ -66,8 +67,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: models = selectedModels.map((id) => $_models.find((m) => m.id === id));
|
$: models = selectedModels.map((id) => $_models.find((m) => m.id === id));
|
||||||
|
|
||||||
onMount(() => {});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="m-auto w-full max-w-6xl px-2 @2xl:px-20 translate-y-6 py-24 text-center">
|
<div class="m-auto w-full max-w-6xl px-2 @2xl:px-20 translate-y-6 py-24 text-center">
|
||||||
|
|
@ -91,8 +90,6 @@
|
||||||
<FolderTitle
|
<FolderTitle
|
||||||
folder={$selectedFolder}
|
folder={$selectedFolder}
|
||||||
onUpdate={async (folder) => {
|
onUpdate={async (folder) => {
|
||||||
selectedFolder.set(folder);
|
|
||||||
|
|
||||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||||
currentChatPage.set(1);
|
currentChatPage.set(1);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
import { selectedFolder } from '$lib/stores';
|
import { selectedFolder } from '$lib/stores';
|
||||||
|
|
||||||
import { deleteFolderById, updateFolderById } from '$lib/apis/folders';
|
import { deleteFolderById, getFolderById, updateFolderById } from '$lib/apis/folders';
|
||||||
import { getChatsByFolderId } from '$lib/apis/chats';
|
import { getChatsByFolderId } from '$lib/apis/chats';
|
||||||
|
|
||||||
import FolderModal from '$lib/components/layout/Sidebar/Folders/FolderModal.svelte';
|
import FolderModal from '$lib/components/layout/Sidebar/Folders/FolderModal.svelte';
|
||||||
|
|
@ -61,8 +61,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success($i18n.t('Folder updated successfully'));
|
toast.success($i18n.t('Folder updated successfully'));
|
||||||
selectedFolder.set(folder);
|
|
||||||
onUpdate(folder);
|
const _folder = await getFolderById(localStorage.token, folder.id).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
await selectedFolder.set(_folder);
|
||||||
|
onUpdate(_folder);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -80,8 +86,14 @@
|
||||||
folder.meta = { ...folder.meta, icon: iconName };
|
folder.meta = { ...folder.meta, icon: iconName };
|
||||||
|
|
||||||
toast.success($i18n.t('Folder updated successfully'));
|
toast.success($i18n.t('Folder updated successfully'));
|
||||||
selectedFolder.set(folder);
|
|
||||||
onUpdate(folder);
|
const _folder = await getFolderById(localStorage.token, folder.id).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
await selectedFolder.set(_folder);
|
||||||
|
onUpdate(_folder);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
export let profileImageUrl;
|
export let profileImageUrl;
|
||||||
export let user = null;
|
export let user = null;
|
||||||
|
|
||||||
|
export let imageClassName = 'size-14 md:size-18';
|
||||||
|
|
||||||
let profileImageInputElement;
|
let profileImageInputElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -89,7 +91,7 @@
|
||||||
<img
|
<img
|
||||||
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(user?.name)}
|
||||||
alt="profile"
|
alt="profile"
|
||||||
class=" rounded-full size-14 md:size-18 object-cover"
|
class=" rounded-full {imageClassName} object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
import Cog6 from '$lib/components/icons/Cog6.svelte';
|
||||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
import AddToolServerModal from '$lib/components/AddToolServerModal.svelte';
|
import AddToolServerModal from '$lib/components/AddToolServerModal.svelte';
|
||||||
|
import WrenchAlt from '$lib/components/icons/WrenchAlt.svelte';
|
||||||
|
|
||||||
export let onDelete = () => {};
|
export let onDelete = () => {};
|
||||||
export let onSubmit = () => {};
|
export let onSubmit = () => {};
|
||||||
|
|
@ -41,33 +42,21 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex w-full gap-2 items-center">
|
<div class="flex w-full gap-2 items-center">
|
||||||
<Tooltip
|
<Tooltip className="w-full relative" content={''} placement="top-start">
|
||||||
className="w-full relative"
|
|
||||||
content={$i18n.t(`WebUI will make requests to "{{url}}"`, {
|
|
||||||
url: `${connection?.url}/${connection?.path ?? 'openapi.json'}`
|
|
||||||
})}
|
|
||||||
placement="top-start"
|
|
||||||
>
|
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="flex-1 relative">
|
<div
|
||||||
<input
|
class="flex-1 relative flex gap-1.5 items-center {!(connection?.config?.enable ?? true)
|
||||||
class=" outline-hidden w-full bg-transparent {!(connection?.config?.enable ?? true)
|
|
||||||
? 'opacity-50'
|
? 'opacity-50'
|
||||||
: ''}"
|
: ''}"
|
||||||
placeholder={$i18n.t('API Base URL')}
|
>
|
||||||
bind:value={connection.url}
|
<Tooltip content={connection?.type === 'mcp' ? $i18n.t('MCP') : $i18n.t('OpenAPI')}>
|
||||||
autocomplete="off"
|
<WrenchAlt />
|
||||||
/>
|
</Tooltip>
|
||||||
|
<div class=" capitalize outline-hidden w-full bg-transparent">
|
||||||
|
{connection?.info?.name ?? connection?.url}
|
||||||
|
<span class="text-gray-500">{connection?.info?.id}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if (connection?.auth_type ?? 'bearer') === 'bearer'}
|
|
||||||
<SensitiveInput
|
|
||||||
inputClassName=" outline-hidden bg-transparent w-full"
|
|
||||||
placeholder={$i18n.t('API Key')}
|
|
||||||
bind:value={connection.key}
|
|
||||||
required={false}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,6 @@ print("${endTag}")
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
console.log(value);
|
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
value = boilerplate;
|
value = boilerplate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,14 +102,14 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" m-auto rounded-2xl max-w-full w-[32rem] mx-2 bg-gray-50 dark:bg-gray-950 max-h-[100dvh] shadow-3xl"
|
class=" m-auto max-w-full w-[32rem] mx-2 bg-white/95 dark:bg-gray-950/95 backdrop-blur-sm rounded-4xl max-h-[100dvh] shadow-3xl border border-white dark:border-gray-900"
|
||||||
in:flyAndScale
|
in:flyAndScale
|
||||||
on:mousedown={(e) => {
|
on:mousedown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="px-[1.75rem] py-6 flex flex-col">
|
<div class="px-[1.75rem] py-6 flex flex-col">
|
||||||
<div class=" text-lg font-semibold dark:text-gray-200 mb-2.5">
|
<div class=" text-lg font-medium dark:text-gray-200 mb-2.5">
|
||||||
{#if title !== ''}
|
{#if title !== ''}
|
||||||
{title}
|
{title}
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -140,7 +140,7 @@
|
||||||
|
|
||||||
<div class="mt-6 flex justify-between gap-1.5">
|
<div class="mt-6 flex justify-between gap-1.5">
|
||||||
<button
|
<button
|
||||||
class="bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2.5 rounded-lg transition"
|
class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2 rounded-3xl transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
show = false;
|
show = false;
|
||||||
dispatch('cancel');
|
dispatch('cancel');
|
||||||
|
|
@ -150,7 +150,7 @@
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
|
class="text-sm bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2 rounded-3xl transition"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
confirmHandler();
|
confirmHandler();
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
import Database from '../icons/Database.svelte';
|
import Database from '../icons/Database.svelte';
|
||||||
import PageEdit from '../icons/PageEdit.svelte';
|
import PageEdit from '../icons/PageEdit.svelte';
|
||||||
import ChatBubble from '../icons/ChatBubble.svelte';
|
import ChatBubble from '../icons/ChatBubble.svelte';
|
||||||
|
import Folder from '../icons/Folder.svelte';
|
||||||
let showModal = false;
|
let showModal = false;
|
||||||
|
|
||||||
const decodeString = (str: string) => {
|
const decodeString = (str: string) => {
|
||||||
|
|
@ -115,6 +116,8 @@
|
||||||
<PageEdit />
|
<PageEdit />
|
||||||
{:else if type === 'chat'}
|
{:else if type === 'chat'}
|
||||||
<ChatBubble />
|
<ChatBubble />
|
||||||
|
{:else if type === 'folder'}
|
||||||
|
<Folder />
|
||||||
{:else}
|
{:else}
|
||||||
<DocumentPage />
|
<DocumentPage />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -672,16 +672,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('content', content);
|
|
||||||
|
|
||||||
if (collaboration && documentId && socket && user) {
|
if (collaboration && documentId && socket && user) {
|
||||||
const { SocketIOCollaborationProvider } = await import('./RichTextInput/Collaboration');
|
const { SocketIOCollaborationProvider } = await import('./RichTextInput/Collaboration');
|
||||||
provider = new SocketIOCollaborationProvider(documentId, socket, user, content);
|
provider = new SocketIOCollaborationProvider(documentId, socket, user, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(bubbleMenuElement, floatingMenuElement);
|
|
||||||
console.log(suggestions);
|
|
||||||
|
|
||||||
editor = new Editor({
|
editor = new Editor({
|
||||||
element: element,
|
element: element,
|
||||||
extensions: [
|
extensions: [
|
||||||
|
|
@ -781,6 +775,8 @@
|
||||||
|
|
||||||
htmlValue = editor.getHTML();
|
htmlValue = editor.getHTML();
|
||||||
jsonValue = editor.getJSON();
|
jsonValue = editor.getJSON();
|
||||||
|
|
||||||
|
if (richText) {
|
||||||
mdValue = turndownService
|
mdValue = turndownService
|
||||||
.turndown(
|
.turndown(
|
||||||
htmlValue
|
htmlValue
|
||||||
|
|
@ -788,6 +784,20 @@
|
||||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||||
)
|
)
|
||||||
.replace(/\u00a0/g, ' ');
|
.replace(/\u00a0/g, ' ');
|
||||||
|
} else {
|
||||||
|
mdValue = turndownService
|
||||||
|
.turndown(
|
||||||
|
htmlValue
|
||||||
|
// Replace empty paragraphs with line breaks
|
||||||
|
.replace(/<p><\/p>/g, '<br/>')
|
||||||
|
// Replace multiple spaces with non-breaking spaces
|
||||||
|
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||||
|
// Replace tabs with non-breaking spaces (preserve indentation)
|
||||||
|
.replace(/\t/g, '\u00a0\u00a0\u00a0\u00a0') // 1 tab = 4 spaces
|
||||||
|
)
|
||||||
|
// Convert non-breaking spaces back to regular spaces for markdown
|
||||||
|
.replace(/\u00a0/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
html: htmlValue,
|
html: htmlValue,
|
||||||
|
|
@ -1035,10 +1045,15 @@
|
||||||
if (!event.clipboardData) return false;
|
if (!event.clipboardData) return false;
|
||||||
if (richText) return false; // Let ProseMirror handle normal copy in rich text mode
|
if (richText) return false; // Let ProseMirror handle normal copy in rich text mode
|
||||||
|
|
||||||
const plain = editor.getText();
|
const { state } = view;
|
||||||
const html = editor.getHTML();
|
const { from, to } = state.selection;
|
||||||
|
|
||||||
event.clipboardData.setData('text/plain', plain.replaceAll('\n\n', '\n'));
|
// Only take the selected text & HTML, not the full doc
|
||||||
|
const plain = state.doc.textBetween(from, to, '\n');
|
||||||
|
const slice = state.doc.cut(from, to);
|
||||||
|
const html = editor.schema ? editor.getHTML(slice) : editor.getHTML(); // depending on your editor API
|
||||||
|
|
||||||
|
event.clipboardData.setData('text/plain', plain);
|
||||||
event.clipboardData.setData('text/html', html);
|
event.clipboardData.setData('text/html', html);
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, tick, getContext } from 'svelte';
|
|
||||||
import { Switch } from 'bits-ui';
|
import { Switch } from 'bits-ui';
|
||||||
|
|
||||||
|
import { createEventDispatcher, tick, getContext } from 'svelte';
|
||||||
import { settings } from '$lib/stores';
|
import { settings } from '$lib/stores';
|
||||||
|
|
||||||
import Tooltip from './Tooltip.svelte';
|
import Tooltip from './Tooltip.svelte';
|
||||||
export let state = true;
|
export let state = true;
|
||||||
export let id = '';
|
export let id = '';
|
||||||
|
|
@ -10,8 +12,6 @@
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: dispatch('change', state);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|
@ -28,6 +28,10 @@
|
||||||
: 'outline outline-1 outline-gray-100 dark:outline-gray-800'} {state
|
: 'outline outline-1 outline-gray-100 dark:outline-gray-800'} {state
|
||||||
? ' bg-emerald-500 dark:bg-emerald-700'
|
? ' bg-emerald-500 dark:bg-emerald-700'
|
||||||
: 'bg-gray-200 dark:bg-transparent'}"
|
: 'bg-gray-200 dark:bg-transparent'}"
|
||||||
|
onCheckedChange={async () => {
|
||||||
|
await tick();
|
||||||
|
dispatch('change', state);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Switch.Thumb
|
<Switch.Thumb
|
||||||
class="pointer-events-none block size-3 shrink-0 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:shadow-mini "
|
class="pointer-events-none block size-3 shrink-0 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:shadow-mini "
|
||||||
|
|
|
||||||
21
src/lib/components/icons/AlignHorizontal.svelte
Normal file
21
src/lib/components/icons/AlignHorizontal.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let className = 'w-4 h-4';
|
||||||
|
export let strokeWidth = '1.5';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path d="M3 22L3 2" stroke-linecap="round" stroke-linejoin="round"></path><path
|
||||||
|
d="M21 22V2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></path><path
|
||||||
|
d="M15 16H9C7.89543 16 7 15.1046 7 14V10C7 8.89543 7.89543 8 9 8H15C16.1046 8 17 8.89543 17 10V14C17 15.1046 16.1046 16 15 16Z"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
21
src/lib/components/icons/AlignVertical.svelte
Normal file
21
src/lib/components/icons/AlignVertical.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let className = 'w-4 h-4';
|
||||||
|
export let strokeWidth = '1.5';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
class={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><path d="M22 3L2 3" stroke-linecap="round" stroke-linejoin="round"></path><path
|
||||||
|
d="M22 21L2 21"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></path><path
|
||||||
|
d="M8 15V9C8 7.89543 8.89543 7 10 7H14C15.1046 7 16 7.89543 16 9V15C16 16.1046 15.1046 17 14 17H10C8.89543 17 8 16.1046 8 15Z"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
|
|
@ -232,7 +232,7 @@
|
||||||
if (chat.id) {
|
if (chat.id) {
|
||||||
let chatObj = null;
|
let chatObj = null;
|
||||||
|
|
||||||
if (chat.id === 'local' || $temporaryChatEnabled) {
|
if ((chat?.id ?? '').startsWith('local') || $temporaryChatEnabled) {
|
||||||
chatObj = chat;
|
chatObj = chat;
|
||||||
} else {
|
} else {
|
||||||
chatObj = await getChatById(localStorage.token, chat.id);
|
chatObj = await getChatById(localStorage.token, chat.id);
|
||||||
|
|
@ -431,9 +431,9 @@
|
||||||
<div class="flex items-center">{$i18n.t('Copy')}</div>
|
<div class="flex items-center">{$i18n.t('Copy')}</div>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
|
{#if !$temporaryChatEnabled && chat?.id}
|
||||||
<hr class="border-gray-50 dark:border-gray-800 my-1" />
|
<hr class="border-gray-50 dark:border-gray-800 my-1" />
|
||||||
|
|
||||||
{#if chat?.id}
|
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
<DropdownMenu.SubTrigger
|
<DropdownMenu.SubTrigger
|
||||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl select-none w-full"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl select-none w-full"
|
||||||
|
|
@ -461,7 +461,6 @@
|
||||||
{/each}
|
{/each}
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||||
|
|
@ -473,7 +472,6 @@
|
||||||
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
{#if !$temporaryChatEnabled}
|
|
||||||
<hr class="border-gray-50 dark:border-gray-800 my-1" />
|
<hr class="border-gray-50 dark:border-gray-800 my-1" />
|
||||||
|
|
||||||
<div class="flex p-1">
|
<div class="flex p-1">
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
_folders.set(folderList);
|
_folders.set(folderList.sort((a, b) => b.updated_at - a.updated_at));
|
||||||
|
|
||||||
folders = {};
|
folders = {};
|
||||||
|
|
||||||
|
|
@ -125,13 +125,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await tick();
|
|
||||||
for (const folderId in folders) {
|
|
||||||
if (folders[folderId] && folders[folderId].is_expanded) {
|
|
||||||
folderRegistry[folderId]?.setFolderItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFolder = async ({ name, data }) => {
|
const createFolder = async ({ name, data }) => {
|
||||||
|
|
@ -185,14 +178,28 @@
|
||||||
|
|
||||||
const initChatList = async () => {
|
const initChatList = async () => {
|
||||||
// Reset pagination variables
|
// Reset pagination variables
|
||||||
tags.set(await getAllTags(localStorage.token));
|
console.log('initChatList');
|
||||||
pinnedChats.set(await getPinnedChatList(localStorage.token));
|
|
||||||
initFolders();
|
|
||||||
|
|
||||||
currentChatPage.set(1);
|
currentChatPage.set(1);
|
||||||
allChatsLoaded = false;
|
allChatsLoaded = false;
|
||||||
|
|
||||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
initFolders();
|
||||||
|
await Promise.all([
|
||||||
|
await (async () => {
|
||||||
|
console.log('Init tags');
|
||||||
|
const _tags = await getAllTags(localStorage.token);
|
||||||
|
tags.set(_tags);
|
||||||
|
})(),
|
||||||
|
await (async () => {
|
||||||
|
console.log('Init pinned chats');
|
||||||
|
const _pinnedChats = await getPinnedChatList(localStorage.token);
|
||||||
|
pinnedChats.set(_pinnedChats);
|
||||||
|
})(),
|
||||||
|
await (async () => {
|
||||||
|
console.log('Init chat list');
|
||||||
|
const _chats = await getChatList(localStorage.token, $currentChatPage);
|
||||||
|
await chats.set(_chats);
|
||||||
|
})()
|
||||||
|
]);
|
||||||
|
|
||||||
// Enable pagination
|
// Enable pagination
|
||||||
scrollPaginationEnabled.set(true);
|
scrollPaginationEnabled.set(true);
|
||||||
|
|
@ -342,9 +349,12 @@
|
||||||
selectedChatId = null;
|
selectedChatId = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let unsubscribers = [];
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
showPinnedChat = localStorage?.showPinnedChat ? localStorage.showPinnedChat === 'true' : true;
|
showPinnedChat = localStorage?.showPinnedChat ? localStorage.showPinnedChat === 'true' : true;
|
||||||
|
await showSidebar.set(!$mobile ? localStorage.sidebar === 'true' : false);
|
||||||
|
|
||||||
|
unsubscribers = [
|
||||||
mobile.subscribe((value) => {
|
mobile.subscribe((value) => {
|
||||||
if ($showSidebar && value) {
|
if ($showSidebar && value) {
|
||||||
showSidebar.set(false);
|
showSidebar.set(false);
|
||||||
|
|
@ -360,9 +370,7 @@
|
||||||
if (!$showSidebar && !value) {
|
if (!$showSidebar && !value) {
|
||||||
showSidebar.set(true);
|
showSidebar.set(true);
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
|
|
||||||
showSidebar.set(!$mobile ? localStorage.sidebar === 'true' : false);
|
|
||||||
showSidebar.subscribe(async (value) => {
|
showSidebar.subscribe(async (value) => {
|
||||||
localStorage.sidebar = value;
|
localStorage.sidebar = value;
|
||||||
|
|
||||||
|
|
@ -381,18 +389,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!value) {
|
if (value) {
|
||||||
await initChannels();
|
await initChannels();
|
||||||
await initChatList();
|
await initChatList();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
];
|
||||||
chats.subscribe((value) => {
|
|
||||||
initFolders();
|
|
||||||
});
|
|
||||||
|
|
||||||
await initChannels();
|
|
||||||
await initChatList();
|
|
||||||
|
|
||||||
window.addEventListener('keydown', onKeyDown);
|
window.addEventListener('keydown', onKeyDown);
|
||||||
window.addEventListener('keyup', onKeyUp);
|
window.addEventListener('keyup', onKeyUp);
|
||||||
|
|
@ -411,6 +413,14 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
if (unsubscribers && unsubscribers.length > 0) {
|
||||||
|
unsubscribers.forEach((unsubscriber) => {
|
||||||
|
if (unsubscriber) {
|
||||||
|
unsubscriber();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.removeEventListener('keydown', onKeyDown);
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,11 +246,12 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
folderRegistry[folderId] = {
|
|
||||||
setFolderItems: () => setFolderItems()
|
|
||||||
};
|
|
||||||
|
|
||||||
open = folders[folderId].is_expanded;
|
open = folders[folderId].is_expanded;
|
||||||
|
folderRegistry[folderId] = {
|
||||||
|
setFolderItems: () => {
|
||||||
|
setFolderItems();
|
||||||
|
}
|
||||||
|
};
|
||||||
if (folderElement) {
|
if (folderElement) {
|
||||||
folderElement.addEventListener('dragover', onDragOver);
|
folderElement.addEventListener('dragover', onDragOver);
|
||||||
folderElement.addEventListener('drop', onDrop);
|
folderElement.addEventListener('drop', onDrop);
|
||||||
|
|
@ -335,7 +336,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
selectedFolder.set(folder);
|
await selectedFolder.set(folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dispatch('update');
|
dispatch('update');
|
||||||
|
|
@ -376,7 +377,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
selectedFolder.set(folder);
|
await selectedFolder.set(folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -488,17 +489,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
clickTimer = setTimeout(async () => {
|
clickTimer = setTimeout(async () => {
|
||||||
await goto('/');
|
|
||||||
|
|
||||||
const folder = await getFolderById(localStorage.token, folderId).catch((error) => {
|
const folder = await getFolderById(localStorage.token, folderId).catch((error) => {
|
||||||
toast.error(`${error}`);
|
toast.error(`${error}`);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
selectedFolder.set(folder);
|
await selectedFolder.set(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await goto('/');
|
||||||
|
|
||||||
if ($mobile) {
|
if ($mobile) {
|
||||||
showSidebar.set(!$showSidebar);
|
showSidebar.set(!$showSidebar);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -583,19 +583,48 @@ ${content}
|
||||||
|
|
||||||
// STEP 1. Get a DOM node to render
|
// STEP 1. Get a DOM node to render
|
||||||
const html = note.data?.content?.html ?? '';
|
const html = note.data?.content?.html ?? '';
|
||||||
|
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||||
|
|
||||||
let node;
|
let node;
|
||||||
if (html instanceof HTMLElement) {
|
if (html instanceof HTMLElement) {
|
||||||
node = html;
|
node = html;
|
||||||
} else {
|
} else {
|
||||||
// If it's HTML string, render to a temporary hidden element
|
const virtualWidth = 800; // px, fixed width for cloned element
|
||||||
|
|
||||||
|
// Clone and style
|
||||||
node = document.createElement('div');
|
node = document.createElement('div');
|
||||||
node.innerHTML = html;
|
|
||||||
|
// title node
|
||||||
|
const titleNode = document.createElement('div');
|
||||||
|
titleNode.textContent = note.title;
|
||||||
|
titleNode.style.fontSize = '24px';
|
||||||
|
titleNode.style.fontWeight = 'medium';
|
||||||
|
titleNode.style.paddingBottom = '20px';
|
||||||
|
titleNode.style.color = isDarkMode ? 'white' : 'black';
|
||||||
|
node.appendChild(titleNode);
|
||||||
|
|
||||||
|
const contentNode = document.createElement('div');
|
||||||
|
|
||||||
|
contentNode.innerHTML = html;
|
||||||
|
|
||||||
|
node.appendChild(contentNode);
|
||||||
|
|
||||||
|
node.classList.add('text-black');
|
||||||
|
node.classList.add('dark:text-white');
|
||||||
|
node.style.width = `${virtualWidth}px`;
|
||||||
|
node.style.position = 'absolute';
|
||||||
|
node.style.left = '-9999px';
|
||||||
|
node.style.height = 'auto';
|
||||||
|
node.style.padding = '40px 40px';
|
||||||
|
|
||||||
|
console.log(node);
|
||||||
document.body.appendChild(node);
|
document.body.appendChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render to canvas with predefined width
|
// Render to canvas with predefined width
|
||||||
const canvas = await html2canvas(node, {
|
const canvas = await html2canvas(node, {
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
|
backgroundColor: isDarkMode ? '#000' : '#fff',
|
||||||
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
scale: 2, // Keep at 1x to avoid unexpected enlargements
|
||||||
width: virtualWidth, // Set fixed virtual screen width
|
width: virtualWidth, // Set fixed virtual screen width
|
||||||
windowWidth: virtualWidth, // Ensure consistent rendering
|
windowWidth: virtualWidth, // Ensure consistent rendering
|
||||||
|
|
@ -612,7 +641,14 @@ ${content}
|
||||||
// A4 page settings
|
// A4 page settings
|
||||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
const pdf = new jsPDF('p', 'mm', 'a4');
|
||||||
const imgWidth = 210; // A4 width in mm
|
const imgWidth = 210; // A4 width in mm
|
||||||
|
const pageWidthMM = 210; // A4 width in mm
|
||||||
const pageHeight = 297; // A4 height in mm
|
const pageHeight = 297; // A4 height in mm
|
||||||
|
const pageHeightMM = 297; // A4 height in mm
|
||||||
|
|
||||||
|
if (isDarkMode) {
|
||||||
|
pdf.setFillColor(0, 0, 0);
|
||||||
|
pdf.rect(0, 0, pageWidthMM, pageHeightMM, 'F'); // black bg
|
||||||
|
}
|
||||||
|
|
||||||
// Maintain aspect ratio
|
// Maintain aspect ratio
|
||||||
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
||||||
|
|
@ -627,6 +663,11 @@ ${content}
|
||||||
position -= pageHeight;
|
position -= pageHeight;
|
||||||
pdf.addPage();
|
pdf.addPage();
|
||||||
|
|
||||||
|
if (isDarkMode) {
|
||||||
|
pdf.setFillColor(0, 0, 0);
|
||||||
|
pdf.rect(0, 0, pageWidthMM, pageHeightMM, 'F'); // black bg
|
||||||
|
}
|
||||||
|
|
||||||
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
||||||
heightLeft -= pageHeight;
|
heightLeft -= pageHeight;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext, tick } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import { WEBUI_NAME, knowledge } from '$lib/stores';
|
import { WEBUI_NAME, knowledge, user } from '$lib/stores';
|
||||||
import {
|
import {
|
||||||
getKnowledgeBases,
|
getKnowledgeBases,
|
||||||
deleteKnowledgeById,
|
deleteKnowledgeById,
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
} from '$lib/apis/knowledge';
|
} from '$lib/apis/knowledge';
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { capitalizeFirstLetter } from '$lib/utils';
|
||||||
|
|
||||||
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
import ItemMenu from './Knowledge/ItemMenu.svelte';
|
import ItemMenu from './Knowledge/ItemMenu.svelte';
|
||||||
|
|
@ -24,9 +25,9 @@
|
||||||
import Search from '../icons/Search.svelte';
|
import Search from '../icons/Search.svelte';
|
||||||
import Plus from '../icons/Plus.svelte';
|
import Plus from '../icons/Plus.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
import { capitalizeFirstLetter } from '$lib/utils';
|
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
|
import ViewSelector from './common/ViewSelector.svelte';
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
|
|
@ -34,14 +35,25 @@
|
||||||
let selectedItem = null;
|
let selectedItem = null;
|
||||||
let showDeleteConfirm = false;
|
let showDeleteConfirm = false;
|
||||||
|
|
||||||
|
let tagsContainerElement: HTMLDivElement;
|
||||||
|
let viewOption = '';
|
||||||
|
|
||||||
let fuse = null;
|
let fuse = null;
|
||||||
|
|
||||||
let knowledgeBases = [];
|
let knowledgeBases = [];
|
||||||
|
|
||||||
|
let items = [];
|
||||||
let filteredItems = [];
|
let filteredItems = [];
|
||||||
|
|
||||||
$: if (knowledgeBases.length > 0) {
|
const setFuse = async () => {
|
||||||
// Added a check for non-empty array, good practice
|
items = knowledgeBases.filter(
|
||||||
fuse = new Fuse(knowledgeBases, {
|
(item) =>
|
||||||
|
viewOption === '' ||
|
||||||
|
(viewOption === 'created' && item.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && item.user_id !== $user?.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
fuse = new Fuse(items, {
|
||||||
keys: [
|
keys: [
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
|
|
@ -50,16 +62,24 @@
|
||||||
],
|
],
|
||||||
threshold: 0.3
|
threshold: 0.3
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
setFilteredItems();
|
||||||
|
};
|
||||||
|
|
||||||
|
$: if (knowledgeBases.length > 0 && viewOption !== undefined) {
|
||||||
|
// Added a check for non-empty array, good practice
|
||||||
|
setFuse();
|
||||||
} else {
|
} else {
|
||||||
fuse = null; // Reset fuse if knowledgeBases is empty
|
fuse = null; // Reset fuse if knowledgeBases is empty
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (fuse) {
|
const setFilteredItems = () => {
|
||||||
filteredItems = query
|
filteredItems = query ? fuse.search(query).map((result) => result.item) : items;
|
||||||
? fuse.search(query).map((e) => {
|
};
|
||||||
return e.item;
|
|
||||||
})
|
$: if (query !== undefined && fuse) {
|
||||||
: knowledgeBases;
|
setFilteredItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteHandler = async (item) => {
|
const deleteHandler = async (item) => {
|
||||||
|
|
@ -75,6 +95,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
viewOption = localStorage?.workspaceViewOption || '';
|
||||||
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
knowledgeBases = await getKnowledgeBaseList(localStorage.token);
|
||||||
loaded = true;
|
loaded = true;
|
||||||
});
|
});
|
||||||
|
|
@ -94,18 +115,35 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 my-1.5">
|
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
|
<div>
|
||||||
{$i18n.t('Knowledge')}
|
{$i18n.t('Knowledge')}
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
</div>
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
|
||||||
>{filteredItems.length}</span
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
>
|
{filteredItems.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full space-x-2">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
<a
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
href="/workspace/knowledge/create"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Knowledge')}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
|
|
@ -128,25 +166,39 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
<button
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
on:wheel={(e) => {
|
||||||
aria-label={$i18n.t('Create Knowledge')}
|
if (e.deltaY !== 0) {
|
||||||
on:click={() => {
|
e.preventDefault();
|
||||||
goto('/workspace/knowledge/create');
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<div
|
||||||
</button>
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap"
|
||||||
</div>
|
bind:this={tagsContainerElement}
|
||||||
|
>
|
||||||
|
<ViewSelector
|
||||||
|
bind:value={viewOption}
|
||||||
|
onChange={async (value) => {
|
||||||
|
localStorage.workspaceViewOption = value;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-5 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-2">
|
{#if (filteredItems ?? []).length !== 0}
|
||||||
|
<!-- The Aleph dreams itself into being, and the void learns its own name -->
|
||||||
|
<div class=" my-2 px-3 grid grid-cols-1 lg:grid-cols-2 gap-2">
|
||||||
{#each filteredItems as item}
|
{#each filteredItems as item}
|
||||||
|
<Tooltip content={item?.description ?? item.name}>
|
||||||
<button
|
<button
|
||||||
class=" flex space-x-4 cursor-pointer text-left w-full px-4 py-3 border border-gray-50 dark:border-gray-850 hover:bg-black/5 dark:hover:bg-white/5 transition rounded-2xl"
|
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (item?.meta?.document) {
|
if (item?.meta?.document) {
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|
@ -160,14 +212,25 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" w-full">
|
<div class=" w-full">
|
||||||
<div class="flex items-center justify-between -mt-1">
|
<div class=" self-center flex-1">
|
||||||
|
<div class="flex items-center justify-between -my-1">
|
||||||
|
<div class=" flex gap-2 items-center">
|
||||||
|
<div>
|
||||||
{#if item?.meta?.document}
|
{#if item?.meta?.document}
|
||||||
<Badge type="muted" content={$i18n.t('Document')} />
|
<Badge type="muted" content={$i18n.t('Document')} />
|
||||||
{:else}
|
{:else}
|
||||||
<Badge type="success" content={$i18n.t('Collection')} />
|
<Badge type="success" content={$i18n.t('Collection')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" flex self-center -mr-1 translate-y-1">
|
<div class=" text-xs text-gray-500 line-clamp-1">
|
||||||
|
{$i18n.t('Updated')}
|
||||||
|
{dayjs(item.updated_at * 1000).fromNow()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class=" flex self-center">
|
||||||
<ItemMenu
|
<ItemMenu
|
||||||
on:delete={() => {
|
on:delete={() => {
|
||||||
selectedItem = item;
|
selectedItem = item;
|
||||||
|
|
@ -176,15 +239,14 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" self-center flex-1 px-1 mb-1">
|
|
||||||
<div class=" font-semibold line-clamp-1 h-fit">{item.name}</div>
|
|
||||||
|
|
||||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
|
||||||
{item.description}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex justify-between">
|
<div class=" flex items-center gap-1 justify-between px-1.5">
|
||||||
|
<div class=" flex items-center gap-2">
|
||||||
|
<div class=" text-sm font-medium line-clamp-1 capitalize">{item.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={item?.user?.email ?? $i18n.t('Deleted User')}
|
content={item?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
|
|
@ -198,18 +260,28 @@
|
||||||
})}
|
})}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class=" text-xs text-gray-500 line-clamp-1">
|
|
||||||
{$i18n.t('Updated')}
|
|
||||||
{dayjs(item.updated_at * 1000).fromNow()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
|
<div class="max-w-md text-center">
|
||||||
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No knowledge found')}</div>
|
||||||
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=" text-gray-500 text-xs mt-1 mb-2">
|
<div class=" text-gray-500 text-xs m-2">
|
||||||
ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")}
|
ⓘ {$i18n.t("Use '#' in the prompt input to load and include your knowledge.")}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@
|
||||||
import { getModels } from '$lib/apis';
|
import { getModels } from '$lib/apis';
|
||||||
import { getGroups } from '$lib/apis/groups';
|
import { getGroups } from '$lib/apis/groups';
|
||||||
|
|
||||||
|
import { capitalizeFirstLetter, copyToClipboard } from '$lib/utils';
|
||||||
|
|
||||||
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
||||||
import ModelMenu from './Models/ModelMenu.svelte';
|
import ModelMenu from './Models/ModelMenu.svelte';
|
||||||
import ModelDeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
import ModelDeleteConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
|
|
@ -34,10 +36,11 @@
|
||||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||||
import Switch from '../common/Switch.svelte';
|
import Switch from '../common/Switch.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
import { capitalizeFirstLetter, copyToClipboard } from '$lib/utils';
|
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
import EyeSlash from '../icons/EyeSlash.svelte';
|
import EyeSlash from '../icons/EyeSlash.svelte';
|
||||||
import Eye from '../icons/Eye.svelte';
|
import Eye from '../icons/Eye.svelte';
|
||||||
|
import ViewSelector from './common/ViewSelector.svelte';
|
||||||
|
import TagSelector from './common/TagSelector.svelte';
|
||||||
|
|
||||||
let shiftKey = false;
|
let shiftKey = false;
|
||||||
|
|
||||||
|
|
@ -49,6 +52,8 @@
|
||||||
|
|
||||||
let models = [];
|
let models = [];
|
||||||
let tags = [];
|
let tags = [];
|
||||||
|
|
||||||
|
let viewOption = '';
|
||||||
let selectedTag = '';
|
let selectedTag = '';
|
||||||
|
|
||||||
let filteredModels = [];
|
let filteredModels = [];
|
||||||
|
|
@ -58,22 +63,28 @@
|
||||||
|
|
||||||
let group_ids = [];
|
let group_ids = [];
|
||||||
|
|
||||||
$: if (models) {
|
$: if (models && query !== undefined && selectedTag !== undefined && viewOption !== undefined) {
|
||||||
|
setFilteredModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFilteredModels = async () => {
|
||||||
filteredModels = models.filter((m) => {
|
filteredModels = models.filter((m) => {
|
||||||
if (query === '' && selectedTag === '') return true;
|
if (query === '' && selectedTag === '' && viewOption === '') return true;
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return (
|
return (
|
||||||
((m.name || '').toLowerCase().includes(lowerQuery) ||
|
((m.name || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(m.user?.name || '').toLowerCase().includes(lowerQuery) || // Search by user name
|
(m.user?.name || '').toLowerCase().includes(lowerQuery) || // Search by user name
|
||||||
(m.user?.email || '').toLowerCase().includes(lowerQuery)) && // Search by user email
|
(m.user?.email || '').toLowerCase().includes(lowerQuery)) && // Search by user email
|
||||||
(selectedTag === '' ||
|
(selectedTag === '' ||
|
||||||
m?.meta?.tags?.some((tag) => tag.name.toLowerCase() === selectedTag.toLowerCase()))
|
m?.meta?.tags?.some((tag) => tag.name.toLowerCase() === selectedTag.toLowerCase())) &&
|
||||||
|
(viewOption === '' ||
|
||||||
|
(viewOption === 'created' && m.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && m.user_id !== $user?.id))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let query = '';
|
let query = '';
|
||||||
|
|
||||||
const deleteModelHandler = async (model) => {
|
const deleteModelHandler = async (model) => {
|
||||||
const res = await deleteModelById(localStorage.token, model.id).catch((e) => {
|
const res = await deleteModelById(localStorage.token, model.id).catch((e) => {
|
||||||
toast.error(`${e}`);
|
toast.error(`${e}`);
|
||||||
|
|
@ -173,11 +184,7 @@
|
||||||
saveAs(blob, `${model.id}-${Date.now()}.json`);
|
saveAs(blob, `${model.id}-${Date.now()}.json`);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
const setTags = () => {
|
||||||
models = await getWorkspaceModels(localStorage.token);
|
|
||||||
let groups = await getGroups(localStorage.token);
|
|
||||||
group_ids = groups.map((group) => group.id);
|
|
||||||
|
|
||||||
if (models) {
|
if (models) {
|
||||||
tags = models
|
tags = models
|
||||||
.filter((model) => !(model?.meta?.hidden ?? false))
|
.filter((model) => !(model?.meta?.hidden ?? false))
|
||||||
|
|
@ -187,7 +194,16 @@
|
||||||
// Remove duplicates and sort
|
// Remove duplicates and sort
|
||||||
tags = Array.from(new Set(tags)).sort((a, b) => a.localeCompare(b));
|
tags = Array.from(new Set(tags)).sort((a, b) => a.localeCompare(b));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
viewOption = localStorage.workspaceViewOption ?? '';
|
||||||
|
|
||||||
|
models = await getWorkspaceModels(localStorage.token);
|
||||||
|
let groups = await getGroups(localStorage.token);
|
||||||
|
group_ids = groups.map((group) => group.id);
|
||||||
|
|
||||||
|
setTags();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
|
|
@ -232,18 +248,107 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 mt-1.5">
|
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
|
||||||
|
<input
|
||||||
|
id="models-import-input"
|
||||||
|
bind:this={modelsImportInputElement}
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
console.log(importFiles);
|
||||||
|
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = async (event) => {
|
||||||
|
let savedModels = JSON.parse(event.target.result);
|
||||||
|
console.log(savedModels);
|
||||||
|
|
||||||
|
for (const model of savedModels) {
|
||||||
|
if (model?.info ?? false) {
|
||||||
|
if ($_models.find((m) => m.id === model.id)) {
|
||||||
|
await updateModelById(localStorage.token, model.id, model.info).catch((error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createNewModel(localStorage.token, model.info).catch((error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (model?.id && model?.name) {
|
||||||
|
await createNewModel(localStorage.token, model).catch((error) => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _models.set(
|
||||||
|
await getModels(
|
||||||
|
localStorage.token,
|
||||||
|
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
models = await getWorkspaceModels(localStorage.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(importFiles[0]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex items-center md:self-center text-xl font-medium px-0.5">
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
|
<div>
|
||||||
{$i18n.t('Models')}
|
{$i18n.t('Models')}
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
</div>
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
|
||||||
>{filteredModels.length}</span
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
>
|
{filteredModels.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex flex-1 items-center w-full space-x-2">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
{#if $user?.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={() => {
|
||||||
|
modelsImportInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Import')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if models.length}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={async () => {
|
||||||
|
downloadModels(models);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Export')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
href="/workspace/models/create"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Model')}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<div class="px-3.5 flex flex-1 items-center w-full space-x-2 py-0.5 pb-2">
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
|
|
@ -267,21 +372,10 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
|
||||||
href="/workspace/models/create"
|
|
||||||
>
|
|
||||||
<Plus className="size-3.5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if tags.length > 0}
|
|
||||||
<div
|
<div
|
||||||
class=" flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none"
|
||||||
on:wheel={(e) => {
|
on:wheel={(e) => {
|
||||||
if (e.deltaY !== 0) {
|
if (e.deltaY !== 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -290,108 +384,93 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex gap-1 w-fit text-center text-sm font-medium rounded-full"
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-0.5 whitespace-nowrap"
|
||||||
bind:this={tagsContainerElement}
|
bind:this={tagsContainerElement}
|
||||||
>
|
>
|
||||||
<button
|
<ViewSelector
|
||||||
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
bind:value={viewOption}
|
||||||
? ''
|
onChange={async (value) => {
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
localStorage.workspaceViewOption = value;
|
||||||
on:click={() => {
|
|
||||||
selectedTag = '';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{$i18n.t('All')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{#each tags as tag}
|
await tick();
|
||||||
<Tooltip content={tag}>
|
setTags();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if (tags ?? []).length > 0}
|
||||||
|
<TagSelector
|
||||||
|
bind:value={selectedTag}
|
||||||
|
items={tags.map((tag) => {
|
||||||
|
return { value: tag, label: tag };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if (filteredModels ?? []).length !== 0}
|
||||||
|
<div class=" px-3 my-2 gap-1 lg:gap-2 grid lg:grid-cols-2" id="model-list">
|
||||||
|
{#each filteredModels as model (model.id)}
|
||||||
<button
|
<button
|
||||||
class="min-w-fit outline-none p-1.5 {selectedTag === tag
|
class=" flex cursor-pointer dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl w-full p-2.5"
|
||||||
? ''
|
id="model-item-{model.id}"
|
||||||
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selectedTag = tag;
|
if (
|
||||||
|
$user?.role === 'admin' ||
|
||||||
|
model.user_id === $user?.id ||
|
||||||
|
model.access_control.write.group_ids.some((wg) => group_ids.includes(wg))
|
||||||
|
) {
|
||||||
|
goto(`/workspace/models/edit?id=${encodeURIComponent(model.id)}`);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tag.length > 32 ? `${tag.slice(0, 32)}...` : tag}
|
<div class="flex group/item gap-3.5 w-full">
|
||||||
</button>
|
<div class="self-center pl-0.5">
|
||||||
</Tooltip>
|
<div class="flex bg-white rounded-2xl">
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class=" my-2 mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3" id="model-list">
|
|
||||||
{#each filteredModels as model (model.id)}
|
|
||||||
<div
|
<div
|
||||||
class=" flex flex-col cursor-pointer w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:bg-white/5 hover:bg-black/5 rounded-2xl transition"
|
class="{model.is_active ? '' : 'opacity-50 dark:opacity-50'} {model.meta
|
||||||
id="model-item-{model.id}"
|
.profile_image_url !== `${WEBUI_BASE_URL}/static/favicon.png`
|
||||||
>
|
? 'bg-transparent'
|
||||||
<div class="flex gap-4 mt-1 mb-0.5">
|
: 'bg-white'} rounded-2xl"
|
||||||
<div class=" w-10">
|
|
||||||
<div
|
|
||||||
class=" rounded-full object-cover {model.is_active
|
|
||||||
? ''
|
|
||||||
: 'opacity-50 dark:opacity-50'} "
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={model?.meta?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
src={model?.meta?.profile_image_url ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||||
alt="modelfile profile"
|
alt="modelfile profile"
|
||||||
class=" rounded-full w-full h-auto object-cover"
|
class=" rounded-2xl size-12 object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" shrink-0 flex w-full min-w-0 flex-1 pr-1 self-center">
|
||||||
|
<div class="flex h-full w-full flex-1 flex-col justify-start self-center group">
|
||||||
|
<div class="flex-1 w-full">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<Tooltip content={model.name} className=" w-fit" placement="top-start">
|
||||||
<a
|
<a
|
||||||
class=" flex flex-1 cursor-pointer w-full"
|
class=" font-semibold line-clamp-1 hover:underline capitalize"
|
||||||
href={`/?models=${encodeURIComponent(model.id)}`}
|
href={`/?models=${encodeURIComponent(model.id)}`}
|
||||||
>
|
>
|
||||||
<div class=" flex-1 self-center {model.is_active ? '' : 'text-gray-500'}">
|
{model.name}
|
||||||
<Tooltip
|
|
||||||
content={marked.parse(model?.meta?.description ?? model.id)}
|
|
||||||
className=" w-fit"
|
|
||||||
placement="top-start"
|
|
||||||
>
|
|
||||||
<div class=" font-semibold line-clamp-1">{model.name}</div>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<div class="flex gap-1 text-xs overflow-hidden">
|
|
||||||
<div class="line-clamp-1">
|
|
||||||
{#if (model?.meta?.description ?? '').trim()}
|
|
||||||
{model?.meta?.description}
|
|
||||||
{:else}
|
|
||||||
{model.id}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-between items-center -mb-0.5 px-0.5 mt-1.5">
|
|
||||||
<div class=" text-xs mt-0.5">
|
|
||||||
<Tooltip
|
|
||||||
content={model?.user?.email ?? $i18n.t('Deleted User')}
|
|
||||||
className="flex shrink-0"
|
|
||||||
placement="top-start"
|
|
||||||
>
|
|
||||||
<div class="shrink-0 text-gray-500">
|
|
||||||
{$i18n.t('By {{name}}', {
|
|
||||||
name: capitalizeFirstLetter(
|
|
||||||
model?.user?.name ?? model?.user?.email ?? $i18n.t('Deleted User')
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class=" flex items-center gap-1">
|
||||||
|
<div
|
||||||
|
class="flex justify-end w-full {model.is_active ? '' : 'text-gray-500'}"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<div class=""></div>
|
||||||
<div class="flex flex-row gap-0.5 items-center">
|
<div class="flex flex-row gap-0.5 items-center">
|
||||||
{#if shiftKey}
|
{#if shiftKey}
|
||||||
<Tooltip content={model?.meta?.hidden ? $i18n.t('Show') : $i18n.t('Hide')}>
|
<Tooltip
|
||||||
|
content={model?.meta?.hidden ? $i18n.t('Show') : $i18n.t('Hide')}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
class="self-center w-fit text-sm p-1.5 dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
hideModelHandler(model);
|
hideModelHandler(model);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -405,9 +484,10 @@
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Delete')}>
|
<Tooltip content={$i18n.t('Delete')}>
|
||||||
<button
|
<button
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
class="self-center w-fit text-sm p-1.5 dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
deleteModelHandler(model);
|
deleteModelHandler(model);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -415,29 +495,6 @@
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else}
|
||||||
{#if $user?.role === 'admin' || model.user_id === $user?.id || model.access_control.write.group_ids.some( (wg) => group_ids.includes(wg) )}
|
|
||||||
<a
|
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
href={`/workspace/models/edit?id=${encodeURIComponent(model.id)}`}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<ModelMenu
|
<ModelMenu
|
||||||
user={$user}
|
user={$user}
|
||||||
{model}
|
{model}
|
||||||
|
|
@ -462,19 +519,28 @@
|
||||||
}}
|
}}
|
||||||
onClose={() => {}}
|
onClose={() => {}}
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
class="self-center w-fit text-sm p-1.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
class="self-center w-fit p-1 text-sm dark:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<EllipsisHorizontal className="size-5" />
|
<EllipsisHorizontal className="size-5" />
|
||||||
</button>
|
</div>
|
||||||
</ModelMenu>
|
</ModelMenu>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ml-1">
|
<button
|
||||||
<Tooltip content={model.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip
|
||||||
|
content={model.is_active ? $i18n.t('Enabled') : $i18n.t('Disabled')}
|
||||||
|
>
|
||||||
<Switch
|
<Switch
|
||||||
bind:state={model.is_active}
|
bind:state={model.is_active}
|
||||||
on:change={async (e) => {
|
on:change={async () => {
|
||||||
toggleModelById(localStorage.token, model.id);
|
toggleModelById(localStorage.token, model.id);
|
||||||
_models.set(
|
_models.set(
|
||||||
await getModels(
|
await getModels(
|
||||||
|
|
@ -486,122 +552,62 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" flex gap-1 pr-2 -mt-1 items-center">
|
||||||
|
<Tooltip
|
||||||
|
content={model?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
|
className="flex shrink-0"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<div class="shrink-0 text-gray-500 text-xs">
|
||||||
|
{$i18n.t('By {{name}}', {
|
||||||
|
name: capitalizeFirstLetter(
|
||||||
|
model?.user?.name ?? model?.user?.email ?? $i18n.t('Deleted User')
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<div>·</div>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
content={marked.parse(model?.meta?.description ?? model.id)}
|
||||||
|
className=" w-fit text-left"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<div class="flex gap-1 text-xs overflow-hidden">
|
||||||
|
<div class="line-clamp-1">
|
||||||
|
{#if (model?.meta?.description ?? '').trim()}
|
||||||
|
{model?.meta?.description}
|
||||||
|
{:else}
|
||||||
|
{model.id}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
{#if $user?.role === 'admin'}
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
<div class=" flex justify-end w-full mb-3">
|
<div class="max-w-md text-center">
|
||||||
<div class="flex space-x-1">
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
<input
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No models found')}</div>
|
||||||
id="models-import-input"
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
bind:this={modelsImportInputElement}
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
bind:files={importFiles}
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
hidden
|
|
||||||
on:change={() => {
|
|
||||||
console.log(importFiles);
|
|
||||||
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = async (event) => {
|
|
||||||
let savedModels = JSON.parse(event.target.result);
|
|
||||||
console.log(savedModels);
|
|
||||||
|
|
||||||
for (const model of savedModels) {
|
|
||||||
if (model?.info ?? false) {
|
|
||||||
if ($_models.find((m) => m.id === model.id)) {
|
|
||||||
await updateModelById(localStorage.token, model.id, model.info).catch(
|
|
||||||
(error) => {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await createNewModel(localStorage.token, model.info).catch((error) => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (model?.id && model?.name) {
|
|
||||||
await createNewModel(localStorage.token, model).catch((error) => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _models.set(
|
|
||||||
await getModels(
|
|
||||||
localStorage.token,
|
|
||||||
$config?.features?.enable_direct_connections &&
|
|
||||||
($settings?.directConnections ?? null)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
models = await getWorkspaceModels(localStorage.token);
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(importFiles[0]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={() => {
|
|
||||||
modelsImportInputElement.click();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Models')}</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-3.5 h-3.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if models.length}
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={async () => {
|
|
||||||
downloadModels(models);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
|
||||||
{$i18n.t('Export Models')} ({models.length})
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-3.5 h-3.5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $config?.features.enable_community_sharing}
|
{#if $config?.features.enable_community_sharing}
|
||||||
<div class=" my-16">
|
<div class=" my-16">
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,7 @@
|
||||||
<option value={null} class=" text-gray-900"
|
<option value={null} class=" text-gray-900"
|
||||||
>{$i18n.t('Select a base model')}</option
|
>{$i18n.t('Select a base model')}</option
|
||||||
>
|
>
|
||||||
{#each $models.filter((m) => (model ? m.id !== model.id : true) && !m?.preset && m?.owned_by !== 'arena') as model}
|
{#each $models.filter((m) => (model ? m.id !== model.id : true) && !m?.preset && m?.owned_by !== 'arena' && !(m?.direct ?? false)) as model}
|
||||||
<option value={model.id} class=" text-gray-900">{model.name}</option>
|
<option value={model.id} class=" text-gray-900">{model.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,14 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip content={$i18n.t('More')}>
|
<Tooltip content={$i18n.t('More')}>
|
||||||
|
<button
|
||||||
|
on:click={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
show = !show;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
const { saveAs } = fileSaver;
|
const { saveAs } = fileSaver;
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext, tick } from 'svelte';
|
||||||
import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores';
|
import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
getPrompts,
|
getPrompts,
|
||||||
getPromptList
|
getPromptList
|
||||||
} from '$lib/apis/prompts';
|
} from '$lib/apis/prompts';
|
||||||
|
import { capitalizeFirstLetter, slugify } from '$lib/utils';
|
||||||
|
|
||||||
import PromptMenu from './Prompts/PromptMenu.svelte';
|
import PromptMenu from './Prompts/PromptMenu.svelte';
|
||||||
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
|
||||||
|
|
@ -22,9 +23,9 @@
|
||||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import { capitalizeFirstLetter, slugify } from '$lib/utils';
|
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
import GarbageBin from '../icons/GarbageBin.svelte';
|
import GarbageBin from '../icons/GarbageBin.svelte';
|
||||||
|
import ViewSelector from './common/ViewSelector.svelte';
|
||||||
|
|
||||||
let shiftKey = false;
|
let shiftKey = false;
|
||||||
|
|
||||||
|
|
@ -40,17 +41,30 @@
|
||||||
let showDeleteConfirm = false;
|
let showDeleteConfirm = false;
|
||||||
let deletePrompt = null;
|
let deletePrompt = null;
|
||||||
|
|
||||||
|
let tagsContainerElement: HTMLDivElement;
|
||||||
|
let viewOption = '';
|
||||||
|
|
||||||
let filteredItems = [];
|
let filteredItems = [];
|
||||||
$: filteredItems = prompts.filter((p) => {
|
|
||||||
if (query === '') return true;
|
$: if (prompts && query !== undefined && viewOption !== undefined) {
|
||||||
|
setFilteredItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFilteredItems = () => {
|
||||||
|
filteredItems = prompts.filter((p) => {
|
||||||
|
if (query === '' && viewOption === '') return true;
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return (
|
return (
|
||||||
(p.title || '').toLowerCase().includes(lowerQuery) ||
|
((p.title || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(p.command || '').toLowerCase().includes(lowerQuery) ||
|
(p.command || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(p.user?.name || '').toLowerCase().includes(lowerQuery) ||
|
(p.user?.name || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(p.user?.email || '').toLowerCase().includes(lowerQuery)
|
(p.user?.email || '').toLowerCase().includes(lowerQuery)) &&
|
||||||
|
(viewOption === '' ||
|
||||||
|
(viewOption === 'created' && p.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && p.user_id !== $user?.id))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const shareHandler = async (prompt) => {
|
const shareHandler = async (prompt) => {
|
||||||
toast.success($i18n.t('Redirecting you to Open WebUI Community'));
|
toast.success($i18n.t('Redirecting you to Open WebUI Community'));
|
||||||
|
|
@ -111,6 +125,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
viewOption = localStorage?.workspaceViewOption || '';
|
||||||
await init();
|
await init();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
|
|
@ -161,18 +176,99 @@
|
||||||
</div>
|
</div>
|
||||||
</DeleteConfirmDialog>
|
</DeleteConfirmDialog>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1 my-1.5">
|
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
|
||||||
|
<input
|
||||||
|
id="prompts-import-input"
|
||||||
|
bind:this={promptsImportInputElement}
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
console.log(importFiles);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (event) => {
|
||||||
|
const savedPrompts = JSON.parse(event.target.result);
|
||||||
|
console.log(savedPrompts);
|
||||||
|
|
||||||
|
for (const prompt of savedPrompts) {
|
||||||
|
await createNewPrompt(localStorage.token, {
|
||||||
|
command: prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
|
||||||
|
title: prompt.title,
|
||||||
|
content: prompt.content
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts = await getPromptList(localStorage.token);
|
||||||
|
await _prompts.set(await getPrompts(localStorage.token));
|
||||||
|
|
||||||
|
importFiles = [];
|
||||||
|
promptsImportInputElement.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(importFiles[0]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
|
<div>
|
||||||
{$i18n.t('Prompts')}
|
{$i18n.t('Prompts')}
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
</div>
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
|
||||||
>{filteredItems.length}</span
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
>
|
{filteredItems.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full space-x-2">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
{#if $user?.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={() => {
|
||||||
|
promptsImportInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Import')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if prompts.length}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={async () => {
|
||||||
|
let blob = new Blob([JSON.stringify(prompts)], {
|
||||||
|
type: 'application/json'
|
||||||
|
});
|
||||||
|
saveAs(blob, `prompts-export-${Date.now()}.json`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Export')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
href="/workspace/prompts/create"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Prompt')}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
|
|
@ -196,33 +292,49 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
|
||||||
href="/workspace/prompts/create"
|
|
||||||
>
|
|
||||||
<Plus className="size-3.5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3">
|
|
||||||
{#each filteredItems as prompt}
|
|
||||||
<div
|
<div
|
||||||
class=" flex space-x-4 cursor-pointer w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:bg-white/5 hover:bg-black/5 rounded-2xl transition"
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
|
||||||
|
on:wheel={(e) => {
|
||||||
|
if (e.deltaY !== 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
|
<div
|
||||||
<a href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap"
|
||||||
<div class=" flex-1 flex items-center gap-2 self-center">
|
bind:this={tagsContainerElement}
|
||||||
<div class=" font-semibold line-clamp-1 capitalize">{prompt.title}</div>
|
>
|
||||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
<ViewSelector
|
||||||
|
bind:value={viewOption}
|
||||||
|
onChange={async (value) => {
|
||||||
|
localStorage.workspaceViewOption = value;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if (filteredItems ?? []).length !== 0}
|
||||||
|
<!-- Before they call, I will answer; while they are yet speaking, I will hear. -->
|
||||||
|
<div class="gap-2 grid my-2 px-3 lg:grid-cols-2">
|
||||||
|
{#each filteredItems as prompt}
|
||||||
|
<a
|
||||||
|
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl"
|
||||||
|
href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
|
||||||
|
>
|
||||||
|
<div class=" flex flex-col flex-1 space-x-4 cursor-pointer w-full pl-1">
|
||||||
|
<div class=" flex-1 flex items-center gap-2 self-start">
|
||||||
|
<div class=" font-medium line-clamp-1 capitalize">{prompt.title}</div>
|
||||||
|
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1 text-gray-500">
|
||||||
{prompt.command}
|
{prompt.command}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" text-xs px-0.5">
|
<div class=" text-xs">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={prompt?.user?.email ?? $i18n.t('Deleted User')}
|
content={prompt?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
className="flex shrink-0"
|
className="flex shrink-0"
|
||||||
|
|
@ -237,7 +349,6 @@
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-0.5 self-center">
|
<div class="flex flex-row gap-0.5 self-center">
|
||||||
{#if shiftKey}
|
{#if shiftKey}
|
||||||
|
|
@ -253,27 +364,6 @@
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
|
||||||
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
|
|
||||||
type="button"
|
|
||||||
href={`/workspace/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<PromptMenu
|
<PromptMenu
|
||||||
shareHandler={() => {
|
shareHandler={() => {
|
||||||
shareHandler(prompt);
|
shareHandler(prompt);
|
||||||
|
|
@ -299,108 +389,21 @@
|
||||||
</PromptMenu>
|
</PromptMenu>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
{#if $user?.role === 'admin'}
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
<div class=" flex justify-end w-full mb-3">
|
<div class="max-w-md text-center">
|
||||||
<div class="flex space-x-2">
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
<input
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No prompts found')}</div>
|
||||||
id="prompts-import-input"
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
bind:this={promptsImportInputElement}
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
bind:files={importFiles}
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
hidden
|
|
||||||
on:change={() => {
|
|
||||||
console.log(importFiles);
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = async (event) => {
|
|
||||||
const savedPrompts = JSON.parse(event.target.result);
|
|
||||||
console.log(savedPrompts);
|
|
||||||
|
|
||||||
for (const prompt of savedPrompts) {
|
|
||||||
await createNewPrompt(localStorage.token, {
|
|
||||||
command:
|
|
||||||
prompt.command.charAt(0) === '/' ? prompt.command.slice(1) : prompt.command,
|
|
||||||
title: prompt.title,
|
|
||||||
content: prompt.content
|
|
||||||
}).catch((error) => {
|
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
prompts = await getPromptList(localStorage.token);
|
|
||||||
await _prompts.set(await getPrompts(localStorage.token));
|
|
||||||
|
|
||||||
importFiles = [];
|
|
||||||
promptsImportInputElement.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(importFiles[0]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={() => {
|
|
||||||
promptsImportInputElement.click();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Prompts')}</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if prompts.length}
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={async () => {
|
|
||||||
let blob = new Blob([JSON.stringify(prompts)], {
|
|
||||||
type: 'application/json'
|
|
||||||
});
|
|
||||||
saveAs(blob, `prompts-export-${Date.now()}.json`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
|
||||||
{$i18n.t('Export Prompts')} ({prompts.length})
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $config?.features.enable_community_sharing}
|
{#if $config?.features.enable_community_sharing}
|
||||||
<div class=" my-16">
|
<div class=" my-16">
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
import fileSaver from 'file-saver';
|
import fileSaver from 'file-saver';
|
||||||
const { saveAs } = fileSaver;
|
const { saveAs } = fileSaver;
|
||||||
|
|
||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext, tick } from 'svelte';
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
import { WEBUI_NAME, config, prompts, tools as _tools, user } from '$lib/stores';
|
import { WEBUI_NAME, config, prompts, tools as _tools, user } from '$lib/stores';
|
||||||
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
|
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import {
|
import {
|
||||||
|
|
@ -17,7 +18,8 @@
|
||||||
getToolList,
|
getToolList,
|
||||||
getTools
|
getTools
|
||||||
} from '$lib/apis/tools';
|
} from '$lib/apis/tools';
|
||||||
import Download from '../icons/Download.svelte';
|
import { capitalizeFirstLetter } from '$lib/utils';
|
||||||
|
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import ConfirmDialog from '../common/ConfirmDialog.svelte';
|
import ConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
import ToolMenu from './Tools/ToolMenu.svelte';
|
import ToolMenu from './Tools/ToolMenu.svelte';
|
||||||
|
|
@ -31,12 +33,10 @@
|
||||||
import Plus from '../icons/Plus.svelte';
|
import Plus from '../icons/Plus.svelte';
|
||||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||||
import Spinner from '../common/Spinner.svelte';
|
import Spinner from '../common/Spinner.svelte';
|
||||||
import { capitalizeFirstLetter } from '$lib/utils';
|
|
||||||
import XMark from '../icons/XMark.svelte';
|
import XMark from '../icons/XMark.svelte';
|
||||||
import AddToolMenu from './Tools/AddToolMenu.svelte';
|
import AddToolMenu from './Tools/AddToolMenu.svelte';
|
||||||
import ImportModal from '../ImportModal.svelte';
|
import ImportModal from '../ImportModal.svelte';
|
||||||
|
import ViewSelector from './common/ViewSelector.svelte';
|
||||||
const i18n = getContext('i18n');
|
|
||||||
|
|
||||||
let shiftKey = false;
|
let shiftKey = false;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
@ -56,18 +56,30 @@
|
||||||
let tools = [];
|
let tools = [];
|
||||||
let filteredItems = [];
|
let filteredItems = [];
|
||||||
|
|
||||||
|
let tagsContainerElement: HTMLDivElement;
|
||||||
|
let viewOption = '';
|
||||||
|
|
||||||
let showImportModal = false;
|
let showImportModal = false;
|
||||||
|
|
||||||
$: filteredItems = tools.filter((t) => {
|
$: if (tools && query !== undefined && viewOption !== undefined) {
|
||||||
if (query === '') return true;
|
setFilteredItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFilteredItems = () => {
|
||||||
|
filteredItems = tools.filter((t) => {
|
||||||
|
if (query === '' && viewOption === '') return true;
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return (
|
return (
|
||||||
(t.name || '').toLowerCase().includes(lowerQuery) ||
|
((t.name || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(t.id || '').toLowerCase().includes(lowerQuery) ||
|
(t.id || '').toLowerCase().includes(lowerQuery) ||
|
||||||
(t.user?.name || '').toLowerCase().includes(lowerQuery) || // Search by user name
|
(t.user?.name || '').toLowerCase().includes(lowerQuery) || // Search by user name
|
||||||
(t.user?.email || '').toLowerCase().includes(lowerQuery) // Search by user email
|
(t.user?.email || '').toLowerCase().includes(lowerQuery)) && // Search by user email
|
||||||
|
(viewOption === '' ||
|
||||||
|
(viewOption === 'created' && t.user_id === $user?.id) ||
|
||||||
|
(viewOption === 'shared' && t.user_id !== $user?.id))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const shareHandler = async (tool) => {
|
const shareHandler = async (tool) => {
|
||||||
const item = await getToolById(localStorage.token, tool.id).catch((error) => {
|
const item = await getToolById(localStorage.token, tool.id).catch((error) => {
|
||||||
|
|
@ -141,6 +153,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
viewOption = localStorage?.workspaceViewOption || '';
|
||||||
await init();
|
await init();
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
|
||||||
|
|
@ -193,18 +206,104 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<div class="flex flex-col gap-1 my-1.5">
|
<div class="flex flex-col gap-1 px-1 mt-1.5 mb-3">
|
||||||
|
<input
|
||||||
|
id="documents-import-input"
|
||||||
|
bind:this={toolsImportInputElement}
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".json"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
console.log(importFiles);
|
||||||
|
showConfirm = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex md:self-center text-xl font-medium px-0.5 items-center">
|
<div class="flex items-center md:self-center text-xl font-medium px-0.5 gap-2 shrink-0">
|
||||||
|
<div>
|
||||||
{$i18n.t('Tools')}
|
{$i18n.t('Tools')}
|
||||||
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-50 dark:bg-gray-850" />
|
</div>
|
||||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
|
|
||||||
>{filteredItems.length}</span
|
<div class="text-lg font-medium text-gray-500 dark:text-gray-500">
|
||||||
>
|
{filteredItems.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" flex w-full space-x-2">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
{#if $user?.role === 'admin'}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={() => {
|
||||||
|
toolsImportInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Import')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if tools.length}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={async () => {
|
||||||
|
const _tools = await exportTools(localStorage.token).catch((error) => {
|
||||||
|
toast.error(`${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_tools) {
|
||||||
|
let blob = new Blob([JSON.stringify(_tools)], {
|
||||||
|
type: 'application/json'
|
||||||
|
});
|
||||||
|
saveAs(blob, `tools-export-${Date.now()}.json`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Export')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $user?.role === 'admin'}
|
||||||
|
<AddToolMenu
|
||||||
|
createHandler={() => {
|
||||||
|
goto('/workspace/tools/create');
|
||||||
|
}}
|
||||||
|
importFromLinkHandler={() => {
|
||||||
|
showImportModal = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Tool')}</div>
|
||||||
|
</div>
|
||||||
|
</AddToolMenu>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
class=" px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-medium text-sm flex items-center"
|
||||||
|
href="/workspace/tools/create"
|
||||||
|
>
|
||||||
|
<Plus className="size-3" strokeWidth="2.5" />
|
||||||
|
|
||||||
|
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Tool')}</div></a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="py-2 bg-white dark:bg-gray-900 rounded-3xl border border-gray-100 dark:border-gray-850"
|
||||||
|
>
|
||||||
|
<!-- The iron remembers its forge. -->
|
||||||
|
<div class=" flex w-full space-x-2 py-0.5 px-3.5 pb-2">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
<div class=" self-center ml-1 mr-3">
|
<div class=" self-center ml-1 mr-3">
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
|
|
@ -227,39 +326,38 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
{#if $user?.role === 'admin'}
|
class="px-3 flex w-full bg-transparent overflow-x-auto scrollbar-none -mx-1"
|
||||||
<AddToolMenu
|
on:wheel={(e) => {
|
||||||
createHandler={() => {
|
if (e.deltaY !== 0) {
|
||||||
goto('/workspace/tools/create');
|
e.preventDefault();
|
||||||
}}
|
e.currentTarget.scrollLeft += e.deltaY;
|
||||||
importFromLinkHandler={() => {
|
}
|
||||||
showImportModal = true;
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
class="flex gap-0.5 w-fit text-center text-sm rounded-full bg-transparent px-1.5 whitespace-nowrap"
|
||||||
|
bind:this={tagsContainerElement}
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<ViewSelector
|
||||||
</div>
|
bind:value={viewOption}
|
||||||
</AddToolMenu>
|
onChange={async (value) => {
|
||||||
{:else}
|
localStorage.workspaceViewOption = value;
|
||||||
<a
|
|
||||||
class=" px-2 py-2 rounded-xl hover:bg-gray-700/10 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition font-medium text-sm flex items-center space-x-1"
|
await tick();
|
||||||
href="/workspace/tools/create"
|
}}
|
||||||
>
|
/>
|
||||||
<Plus className="size-3.5" />
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-5 gap-2 grid lg:grid-cols-2 xl:grid-cols-3">
|
{#if (filteredItems ?? []).length !== 0}
|
||||||
|
<div class=" my-2 gap-2 grid px-3 lg:grid-cols-2">
|
||||||
{#each filteredItems as tool}
|
{#each filteredItems as tool}
|
||||||
|
<Tooltip content={tool?.meta?.description ?? tool?.id}>
|
||||||
<div
|
<div
|
||||||
class=" flex space-x-4 cursor-pointer w-full px-4 py-3 border border-gray-50 dark:border-gray-850 dark:hover:bg-white/5 hover:bg-black/5 rounded-2xl transition"
|
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2.5 dark:hover:bg-gray-850/50 hover:bg-gray-50 transition rounded-2xl"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
|
class=" flex flex-1 space-x-3.5 cursor-pointer w-full"
|
||||||
|
|
@ -267,37 +365,20 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center text-left">
|
<div class="flex items-center text-left">
|
||||||
<div class=" flex-1 self-center">
|
<div class=" flex-1 self-center">
|
||||||
<Tooltip content={tool?.meta?.description ?? ''} placement="top-start">
|
<Tooltip content={tool.id} placement="top-start">
|
||||||
<div class=" font-semibold flex items-center gap-1.5">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div class="line-clamp-1 text-sm">
|
||||||
class=" text-xs font-semibold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
|
{tool.name}
|
||||||
>
|
|
||||||
TOOL
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if tool?.meta?.manifest?.version}
|
{#if tool?.meta?.manifest?.version}
|
||||||
<div
|
<div class=" text-gray-500 text-xs font-medium shrink-0">
|
||||||
class="text-xs font-semibold px-1 rounded-sm line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
v{tool?.meta?.manifest?.version ?? ''}
|
v{tool?.meta?.manifest?.version ?? ''}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="line-clamp-1">
|
|
||||||
{tool.name}
|
|
||||||
|
|
||||||
<span class=" text-gray-500 text-xs font-medium shrink-0">{tool.id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div class="px-0.5">
|
<div class="px-0.5">
|
||||||
<div class="flex gap-1.5 mt-0.5 mb-0.5">
|
|
||||||
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
|
|
||||||
{tool.meta.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-xs text-gray-500 shrink-0">
|
<div class="text-xs text-gray-500 shrink-0">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={tool?.user?.email ?? $i18n.t('Deleted User')}
|
content={tool?.user?.email ?? $i18n.t('Deleted User')}
|
||||||
|
|
@ -404,89 +485,21 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
{#if $user?.role === 'admin'}
|
<div class=" w-full h-full flex flex-col justify-center items-center my-16 mb-24">
|
||||||
<div class=" flex justify-end w-full mb-2">
|
<div class="max-w-md text-center">
|
||||||
<div class="flex space-x-2">
|
<div class=" text-3xl mb-3">😕</div>
|
||||||
<input
|
<div class=" text-lg font-medium mb-1">{$i18n.t('No tools found')}</div>
|
||||||
id="documents-import-input"
|
<div class=" text-gray-500 text-center text-xs">
|
||||||
bind:this={toolsImportInputElement}
|
{$i18n.t('Try adjusting your search or filter to find what you are looking for.')}
|
||||||
bind:files={importFiles}
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
hidden
|
|
||||||
on:change={() => {
|
|
||||||
console.log(importFiles);
|
|
||||||
showConfirm = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={() => {
|
|
||||||
toolsImportInputElement.click();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Import Tools')}</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if tools.length}
|
|
||||||
<button
|
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
|
||||||
on:click={async () => {
|
|
||||||
const _tools = await exportTools(localStorage.token).catch((error) => {
|
|
||||||
toast.error(`${error}`);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_tools) {
|
|
||||||
let blob = new Blob([JSON.stringify(_tools)], {
|
|
||||||
type: 'application/json'
|
|
||||||
});
|
|
||||||
saveAs(blob, `tools-export-${Date.now()}.json`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
|
||||||
{$i18n.t('Export Tools')} ({tools.length})
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" self-center">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="w-4 h-4"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $config?.features.enable_community_sharing}
|
{#if $config?.features.enable_community_sharing}
|
||||||
<div class=" my-16">
|
<div class=" my-16">
|
||||||
|
|
|
||||||
|
|
@ -41,27 +41,27 @@
|
||||||
|
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
class="w-full max-w-[190px] text-sm rounded-xl px-1 py-1 dark:text-white shadow-lg border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850"
|
class="w-full max-w-[190px] rounded-2xl px-1 py-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||||
sideOffset={-2}
|
sideOffset={6}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="start"
|
||||||
transition={flyAndScale}
|
transition={flyAndScale}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
createHandler();
|
createHandler();
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class=" self-center mr-2">
|
<div class=" self-center mr-2">
|
||||||
<PencilSolid />
|
<Pencil />
|
||||||
</div>
|
</div>
|
||||||
<div class=" self-center truncate">{$i18n.t('New Tool')}</div>
|
<div class=" self-center truncate">{$i18n.t('New Tool')}</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl w-full"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
importFromLinkHandler();
|
importFromLinkHandler();
|
||||||
show = false;
|
show = false;
|
||||||
|
|
|
||||||
106
src/lib/components/workspace/common/TagSelector.svelte
Normal file
106
src/lib/components/workspace/common/TagSelector.svelte
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Select } from 'bits-ui';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||||
|
import Check from '$lib/components/icons/Check.svelte';
|
||||||
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
|
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
export let value = '';
|
||||||
|
export let placeholder = $i18n.t('Tag');
|
||||||
|
export let onChange: (value: string) => void = () => {};
|
||||||
|
|
||||||
|
export let items = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select.Root
|
||||||
|
selected={value ? items.find((item) => item.value === value) : null}
|
||||||
|
{items}
|
||||||
|
onSelectedChange={(selectedItem) => {
|
||||||
|
value = selectedItem.value;
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger
|
||||||
|
class="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 rounded-xl "
|
||||||
|
aria-label={placeholder}
|
||||||
|
>
|
||||||
|
<Select.Value
|
||||||
|
class="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden capitalize"
|
||||||
|
{placeholder}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if value}
|
||||||
|
<button
|
||||||
|
class="outline-none"
|
||||||
|
on:click={() => {
|
||||||
|
value = '';
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<XMark className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<ChevronDown className=" size-3.5" strokeWidth="2.5" />
|
||||||
|
{/if}
|
||||||
|
</Select.Trigger>
|
||||||
|
|
||||||
|
<Select.Content
|
||||||
|
class="rounded-2xl min-w-[170px] p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||||
|
sameWidth={false}
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
{#each items as item}
|
||||||
|
<Select.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl capitalize"
|
||||||
|
value={item.value}
|
||||||
|
label={item.label}
|
||||||
|
>
|
||||||
|
{item.label.length > 32 ? `${item.label.slice(0, 32)}...` : item.label}
|
||||||
|
|
||||||
|
{#if value === item.value}
|
||||||
|
<div class="ml-auto">
|
||||||
|
<Check />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</slot>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
|
||||||
|
<!-- <button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('All')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Created by you')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Shared with you')}
|
||||||
|
</button> -->
|
||||||
96
src/lib/components/workspace/common/ViewSelector.svelte
Normal file
96
src/lib/components/workspace/common/ViewSelector.svelte
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Select } from 'bits-ui';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||||
|
import Check from '$lib/components/icons/Check.svelte';
|
||||||
|
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
export let value = '';
|
||||||
|
export let placeholder = $i18n.t('Select view');
|
||||||
|
export let onChange: (value: string) => void = () => {};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ value: '', label: $i18n.t('All') },
|
||||||
|
{ value: 'created', label: $i18n.t('Created by you') },
|
||||||
|
{ value: 'shared', label: $i18n.t('Shared with you') }
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select.Root
|
||||||
|
selected={items.find((item) => item.value === value)}
|
||||||
|
{items}
|
||||||
|
onSelectedChange={(selectedItem) => {
|
||||||
|
value = selectedItem.value;
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger
|
||||||
|
class="relative w-full flex items-center gap-0.5 px-2.5 py-1.5 bg-gray-50 dark:bg-gray-850 rounded-xl "
|
||||||
|
aria-label={placeholder}
|
||||||
|
>
|
||||||
|
<Select.Value
|
||||||
|
class="inline-flex h-input px-0.5 w-full outline-hidden bg-transparent truncate placeholder-gray-400 focus:outline-hidden"
|
||||||
|
{placeholder}
|
||||||
|
/>
|
||||||
|
<ChevronDown className=" size-3.5" strokeWidth="2.5" />
|
||||||
|
</Select.Trigger>
|
||||||
|
|
||||||
|
<Select.Content
|
||||||
|
class="rounded-2xl min-w-[170px] p-1 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
|
||||||
|
sameWidth={false}
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
{#each items as item}
|
||||||
|
<Select.Item
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||||
|
value={item.value}
|
||||||
|
label={item.label}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
|
||||||
|
{#if value === item.value}
|
||||||
|
<div class="ml-auto">
|
||||||
|
<Check />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</slot>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
|
||||||
|
<!-- <button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('All')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Created by you')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="min-w-fit outline-none p-1.5 {selectedTag === ''
|
||||||
|
? ''
|
||||||
|
: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
|
||||||
|
on:click={() => {
|
||||||
|
selectedTag = '';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Shared with you')}
|
||||||
|
</button> -->
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "",
|
"Add User Group": "",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين",
|
"Adjusting these settings will apply changes universally to all users.": "سيؤدي ضبط هذه الإعدادات إلى تطبيق التغييرات بشكل عام على كافة المستخدمين",
|
||||||
"admin": "المشرف",
|
"admin": "المشرف",
|
||||||
"Admin": "",
|
"Admin": "",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "",
|
"Create Group": "",
|
||||||
"Create Knowledge": "",
|
"Create Knowledge": "",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "عمل مفتاح جديد",
|
"Create new key": "عمل مفتاح جديد",
|
||||||
"Create new secret key": "عمل سر جديد",
|
"Create new secret key": "عمل سر جديد",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "أنشئت في",
|
"Created at": "أنشئت في",
|
||||||
"Created At": "أنشئت من",
|
"Created At": "أنشئت من",
|
||||||
"Created by": "",
|
"Created by": "",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "",
|
"CSV Import": "",
|
||||||
"Ctrl+Enter to Send": "",
|
"Ctrl+Enter to Send": "",
|
||||||
"Current Model": "الموديل المختار",
|
"Current Model": "الموديل المختار",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "",
|
"Delete function?": "",
|
||||||
"Delete Message": "",
|
"Delete Message": "",
|
||||||
"Delete message?": "",
|
"Delete message?": "",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "",
|
"Delete prompt?": "",
|
||||||
"delete this link": "أحذف هذا الرابط",
|
"delete this link": "أحذف هذا الرابط",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "",
|
"Enter api auth string (e.g. username:password)": "",
|
||||||
"Enter Application DN": "",
|
"Enter Application DN": "",
|
||||||
"Enter Application DN Password": "",
|
"Enter Application DN Password": "",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "",
|
"Export chat (.json)": "",
|
||||||
"Export Chats": "تصدير جميع الدردشات",
|
"Export Chats": "تصدير جميع الدردشات",
|
||||||
"Export Config to JSON File": "",
|
"Export Config to JSON File": "",
|
||||||
"Export Functions": "",
|
|
||||||
"Export Models": "نماذج التصدير",
|
|
||||||
"Export Presets": "",
|
"Export Presets": "",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "مطالبات التصدير",
|
|
||||||
"Export to CSV": "",
|
"Export to CSV": "",
|
||||||
"Export Tools": "",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "فشل في حفظ المحادثة",
|
"Failed to save conversation": "فشل في حفظ المحادثة",
|
||||||
"Failed to save models configuration": "",
|
"Failed to save models configuration": "",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "التركيز على إدخال الدردشة",
|
"Focus chat input": "التركيز على إدخال الدردشة",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "",
|
"Folder deleted successfully": "",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "",
|
"Haptic Feedback": "",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": " {{name}} مرحبا",
|
"Hello, {{name}}": " {{name}} مرحبا",
|
||||||
"Help": "مساعدة",
|
"Help": "مساعدة",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "استيراد الدردشات",
|
"Import Chats": "استيراد الدردشات",
|
||||||
"Import Config from JSON File": "",
|
"Import Config from JSON File": "",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "",
|
|
||||||
"Import Models": "استيراد النماذج",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "",
|
"Import Presets": "",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "مطالبات الاستيراد",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "",
|
|
||||||
"Important Update": "تحديث مهم",
|
"Important Update": "تحديث مهم",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "",
|
"Include": "",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "",
|
"Invalid file format.": "",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "تاق غير صالحة",
|
"Invalid Tag": "تاق غير صالحة",
|
||||||
"is typing...": "",
|
"is typing...": "",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "دردشة جديدة",
|
"New Chat": "دردشة جديدة",
|
||||||
"New Folder": "",
|
"New Folder": "",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "كلمة المرور الجديدة",
|
"New Password": "كلمة المرور الجديدة",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "",
|
"new-channel": "",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "",
|
"No distance available": "",
|
||||||
"No feedbacks found": "",
|
"No feedbacks found": "",
|
||||||
"No file selected": "",
|
"No file selected": "",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "",
|
"No groups with access, add a group to grant access": "",
|
||||||
"No HTML, CSS, or JavaScript content found.": "",
|
"No HTML, CSS, or JavaScript content found.": "",
|
||||||
"No inference engine with management support found": "",
|
"No inference engine with management support found": "",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "",
|
"No models selected": "",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "لا توجد نتائج",
|
"No results": "لا توجد نتائج",
|
||||||
"No results found": "لا توجد نتايج",
|
"No results found": "لا توجد نتايج",
|
||||||
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
||||||
"No source available": "لا يوجد مصدر متاح",
|
"No source available": "لا يوجد مصدر متاح",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "لا توجد مطالبات مقترحة",
|
"No suggestion prompts": "لا توجد مطالبات مقترحة",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "",
|
"No users were found.": "",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "",
|
"No valves to update": "",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "",
|
"Public": "",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
|
||||||
"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
|
"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "",
|
"Query Generation Prompt": "",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1376,6 +1387,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "",
|
"Select Knowledge": "",
|
||||||
"Select only one model to call": "",
|
"Select only one model to call": "",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
|
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "تم",
|
"Send": "تم",
|
||||||
|
|
@ -1416,6 +1428,7 @@
|
||||||
"Share Chat": "مشاركة الدردشة",
|
"Share Chat": "مشاركة الدردشة",
|
||||||
"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
|
"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "",
|
"Sharing Permissions": "",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "عرض",
|
"Show": "عرض",
|
||||||
|
|
@ -1483,6 +1496,7 @@
|
||||||
"System Instructions": "",
|
"System Instructions": "",
|
||||||
"System Prompt": "محادثة النظام",
|
"System Prompt": "محادثة النظام",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "",
|
"Tags": "",
|
||||||
"Tags Generation": "",
|
"Tags Generation": "",
|
||||||
"Tags Generation Prompt": "",
|
"Tags Generation Prompt": "",
|
||||||
|
|
@ -1594,6 +1608,7 @@
|
||||||
"Transformers": "",
|
"Transformers": "",
|
||||||
"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
|
"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
|
||||||
"Trust Proxy Environment": "",
|
"Trust Proxy Environment": "",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "",
|
"TTS Model": "",
|
||||||
"TTS Settings": "TTS اعدادات",
|
"TTS Settings": "TTS اعدادات",
|
||||||
|
|
@ -1630,6 +1645,7 @@
|
||||||
"Upload directory": "",
|
"Upload directory": "",
|
||||||
"Upload files": "",
|
"Upload files": "",
|
||||||
"Upload Files": "تحميل الملفات",
|
"Upload Files": "تحميل الملفات",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "",
|
"Upload Pipeline": "",
|
||||||
"Upload Progress": "جاري التحميل",
|
"Upload Progress": "جاري التحميل",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "إضافة مجموعة مستخدمين",
|
"Add User Group": "إضافة مجموعة مستخدمين",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "تعديل هذه الإعدادات سيطبق التغييرات على جميع المستخدمين بشكل عام.",
|
"Adjusting these settings will apply changes universally to all users.": "تعديل هذه الإعدادات سيطبق التغييرات على جميع المستخدمين بشكل عام.",
|
||||||
"admin": "المسؤول",
|
"admin": "المسؤول",
|
||||||
"Admin": "المسؤول",
|
"Admin": "المسؤول",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "إنشاء مجموعة",
|
"Create Group": "إنشاء مجموعة",
|
||||||
"Create Knowledge": "إنشاء معرفة",
|
"Create Knowledge": "إنشاء معرفة",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "إنشاء مفتاح جديد",
|
"Create new key": "إنشاء مفتاح جديد",
|
||||||
"Create new secret key": "إنشاء مفتاح سري جديد",
|
"Create new secret key": "إنشاء مفتاح سري جديد",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "تم الإنشاء في",
|
"Created at": "تم الإنشاء في",
|
||||||
"Created At": "تاريخ الإنشاء",
|
"Created At": "تاريخ الإنشاء",
|
||||||
"Created by": "تم الإنشاء بواسطة",
|
"Created by": "تم الإنشاء بواسطة",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "استيراد CSV",
|
"CSV Import": "استيراد CSV",
|
||||||
"Ctrl+Enter to Send": "اضغط Ctrl+Enter للإرسال",
|
"Ctrl+Enter to Send": "اضغط Ctrl+Enter للإرسال",
|
||||||
"Current Model": "النموذج الحالي",
|
"Current Model": "النموذج الحالي",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "هل تريد حذف الوظيفة؟",
|
"Delete function?": "هل تريد حذف الوظيفة؟",
|
||||||
"Delete Message": "حذف الرسالة",
|
"Delete Message": "حذف الرسالة",
|
||||||
"Delete message?": "هل تريد حذف الرسالة؟",
|
"Delete message?": "هل تريد حذف الرسالة؟",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "هل تريد حذف الموجه؟",
|
"Delete prompt?": "هل تريد حذف الموجه؟",
|
||||||
"delete this link": "أحذف هذا الرابط",
|
"delete this link": "أحذف هذا الرابط",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
"Enter a detail about yourself for your LLMs to recall": "ادخل معلومات عنك تريد أن يتذكرها الموديل",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "أدخل سلسلة توثيق API (مثال: username:password)",
|
"Enter api auth string (e.g. username:password)": "أدخل سلسلة توثيق API (مثال: username:password)",
|
||||||
"Enter Application DN": "أدخل DN التطبيق",
|
"Enter Application DN": "أدخل DN التطبيق",
|
||||||
"Enter Application DN Password": "أدخل كلمة مرور DN التطبيق",
|
"Enter Application DN Password": "أدخل كلمة مرور DN التطبيق",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "تصدير المحادثة (.json)",
|
"Export chat (.json)": "تصدير المحادثة (.json)",
|
||||||
"Export Chats": "تصدير جميع الدردشات",
|
"Export Chats": "تصدير جميع الدردشات",
|
||||||
"Export Config to JSON File": "تصدير الإعدادات إلى ملف JSON",
|
"Export Config to JSON File": "تصدير الإعدادات إلى ملف JSON",
|
||||||
"Export Functions": "تصدير الوظائف",
|
|
||||||
"Export Models": "نماذج التصدير",
|
|
||||||
"Export Presets": "تصدير الإعدادات المسبقة",
|
"Export Presets": "تصدير الإعدادات المسبقة",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "مطالبات التصدير",
|
|
||||||
"Export to CSV": "تصدير إلى CSV",
|
"Export to CSV": "تصدير إلى CSV",
|
||||||
"Export Tools": "تصدير الأدوات",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "فشل في حفظ المحادثة",
|
"Failed to save conversation": "فشل في حفظ المحادثة",
|
||||||
"Failed to save models configuration": "فشل في حفظ إعدادات النماذج",
|
"Failed to save models configuration": "فشل في حفظ إعدادات النماذج",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "التركيز على إدخال الدردشة",
|
"Focus chat input": "التركيز على إدخال الدردشة",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "تم حذف المجلد بنجاح",
|
"Folder deleted successfully": "تم حذف المجلد بنجاح",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "الاهتزاز اللمسي",
|
"Haptic Feedback": "الاهتزاز اللمسي",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": " {{name}} مرحبا",
|
"Hello, {{name}}": " {{name}} مرحبا",
|
||||||
"Help": "مساعدة",
|
"Help": "مساعدة",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "استيراد الدردشات",
|
"Import Chats": "استيراد الدردشات",
|
||||||
"Import Config from JSON File": "استيراد الإعدادات من ملف JSON",
|
"Import Config from JSON File": "استيراد الإعدادات من ملف JSON",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "استيراد الوظائف",
|
|
||||||
"Import Models": "استيراد النماذج",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "استيراد الإعدادات المسبقة",
|
"Import Presets": "استيراد الإعدادات المسبقة",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "مطالبات الاستيراد",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "استيراد الأدوات",
|
|
||||||
"Important Update": "تحديث مهم",
|
"Important Update": "تحديث مهم",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "تضمين",
|
"Include": "تضمين",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "تنسيق ملف غير صالح.",
|
"Invalid file format.": "تنسيق ملف غير صالح.",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "تاق غير صالحة",
|
"Invalid Tag": "تاق غير صالحة",
|
||||||
"is typing...": "يكتب...",
|
"is typing...": "يكتب...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "دردشة جديدة",
|
"New Chat": "دردشة جديدة",
|
||||||
"New Folder": "مجلد جديد",
|
"New Folder": "مجلد جديد",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "كلمة المرور الجديدة",
|
"New Password": "كلمة المرور الجديدة",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "قناة جديدة",
|
"new-channel": "قناة جديدة",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "لا توجد مسافة متاحة",
|
"No distance available": "لا توجد مسافة متاحة",
|
||||||
"No feedbacks found": "لم يتم العثور على ملاحظات",
|
"No feedbacks found": "لم يتم العثور على ملاحظات",
|
||||||
"No file selected": "لم يتم تحديد ملف",
|
"No file selected": "لم يتم تحديد ملف",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "لا توجد مجموعات لها حق الوصول، أضف مجموعة لمنح الوصول",
|
"No groups with access, add a group to grant access": "لا توجد مجموعات لها حق الوصول، أضف مجموعة لمنح الوصول",
|
||||||
"No HTML, CSS, or JavaScript content found.": "لم يتم العثور على محتوى HTML أو CSS أو JavaScript.",
|
"No HTML, CSS, or JavaScript content found.": "لم يتم العثور على محتوى HTML أو CSS أو JavaScript.",
|
||||||
"No inference engine with management support found": "لم يتم العثور على محرك استدلال يدعم الإدارة",
|
"No inference engine with management support found": "لم يتم العثور على محرك استدلال يدعم الإدارة",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "لم يتم اختيار نماذج",
|
"No models selected": "لم يتم اختيار نماذج",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "لا توجد نتائج",
|
"No results": "لا توجد نتائج",
|
||||||
"No results found": "لا توجد نتايج",
|
"No results found": "لا توجد نتايج",
|
||||||
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
"No search query generated": "لم يتم إنشاء استعلام بحث",
|
||||||
"No source available": "لا يوجد مصدر متاح",
|
"No source available": "لا يوجد مصدر متاح",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "لا توجد مطالبات مقترحة",
|
"No suggestion prompts": "لا توجد مطالبات مقترحة",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "لم يتم العثور على مستخدمين.",
|
"No users were found.": "لم يتم العثور على مستخدمين.",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "لا توجد صمامات للتحديث",
|
"No valves to update": "لا توجد صمامات للتحديث",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "",
|
"Public": "",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com \"{{searchValue}}\" أسحب من ",
|
||||||
"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
|
"Pull a model from Ollama.com": "Ollama.com سحب الموديل من ",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "توجيه إنشاء الاستعلام",
|
"Query Generation Prompt": "توجيه إنشاء الاستعلام",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1376,6 +1387,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "اختر المعرفة",
|
"Select Knowledge": "اختر المعرفة",
|
||||||
"Select only one model to call": "اختر نموذجًا واحدًا فقط للاستدعاء",
|
"Select only one model to call": "اختر نموذجًا واحدًا فقط للاستدعاء",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
|
"Selected model(s) do not support image inputs": "النموذج (النماذج) المحددة لا تدعم مدخلات الصور",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "تم",
|
"Send": "تم",
|
||||||
|
|
@ -1416,6 +1428,7 @@
|
||||||
"Share Chat": "مشاركة الدردشة",
|
"Share Chat": "مشاركة الدردشة",
|
||||||
"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
|
"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "",
|
"Sharing Permissions": "",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "عرض",
|
"Show": "عرض",
|
||||||
|
|
@ -1483,6 +1496,7 @@
|
||||||
"System Instructions": "تعليمات النظام",
|
"System Instructions": "تعليمات النظام",
|
||||||
"System Prompt": "محادثة النظام",
|
"System Prompt": "محادثة النظام",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "",
|
"Tags": "",
|
||||||
"Tags Generation": "إنشاء الوسوم",
|
"Tags Generation": "إنشاء الوسوم",
|
||||||
"Tags Generation Prompt": "توجيه إنشاء الوسوم",
|
"Tags Generation Prompt": "توجيه إنشاء الوسوم",
|
||||||
|
|
@ -1594,6 +1608,7 @@
|
||||||
"Transformers": "Transformers",
|
"Transformers": "Transformers",
|
||||||
"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
|
"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",
|
||||||
"Trust Proxy Environment": "بيئة البروكسي الموثوقة",
|
"Trust Proxy Environment": "بيئة البروكسي الموثوقة",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "نموذج تحويل النص إلى كلام (TTS)",
|
"TTS Model": "نموذج تحويل النص إلى كلام (TTS)",
|
||||||
"TTS Settings": "TTS اعدادات",
|
"TTS Settings": "TTS اعدادات",
|
||||||
|
|
@ -1630,6 +1645,7 @@
|
||||||
"Upload directory": "رفع مجلد",
|
"Upload directory": "رفع مجلد",
|
||||||
"Upload files": "رفع ملفات",
|
"Upload files": "رفع ملفات",
|
||||||
"Upload Files": "تحميل الملفات",
|
"Upload Files": "تحميل الملفات",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "رفع خط المعالجة",
|
"Upload Pipeline": "رفع خط المعالجة",
|
||||||
"Upload Progress": "جاري التحميل",
|
"Upload Progress": "جاري التحميل",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "Добавяне на потребителска група",
|
"Add User Group": "Добавяне на потребителска група",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "При промяна на тези настройки промените се прилагат за всички потребители.",
|
"Adjusting these settings will apply changes universally to all users.": "При промяна на тези настройки промените се прилагат за всички потребители.",
|
||||||
"admin": "админ",
|
"admin": "админ",
|
||||||
"Admin": "Администратор",
|
"Admin": "Администратор",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "Създаване на група",
|
"Create Group": "Създаване на група",
|
||||||
"Create Knowledge": "Създаване на знания",
|
"Create Knowledge": "Създаване на знания",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "Създаване на нов ключ",
|
"Create new key": "Създаване на нов ключ",
|
||||||
"Create new secret key": "Създаване на нов секретен ключ",
|
"Create new secret key": "Създаване на нов секретен ключ",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Създадено на",
|
"Created at": "Създадено на",
|
||||||
"Created At": "Създадено на",
|
"Created At": "Създадено на",
|
||||||
"Created by": "Създадено от",
|
"Created by": "Създадено от",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "Импортиране на CSV",
|
"CSV Import": "Импортиране на CSV",
|
||||||
"Ctrl+Enter to Send": "",
|
"Ctrl+Enter to Send": "",
|
||||||
"Current Model": "Текущ модел",
|
"Current Model": "Текущ модел",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "Изтриване на функцията?",
|
"Delete function?": "Изтриване на функцията?",
|
||||||
"Delete Message": "Изтриване на съобщение",
|
"Delete Message": "Изтриване на съобщение",
|
||||||
"Delete message?": "Изтриване на съобщението?",
|
"Delete message?": "Изтриване на съобщението?",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "Изтриване на промпта?",
|
"Delete prompt?": "Изтриване на промпта?",
|
||||||
"delete this link": "Изтриване на този линк",
|
"delete this link": "Изтриване на този линк",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "Въведете подробности за себе си, за да ги запомнят вашите LLMs",
|
"Enter a detail about yourself for your LLMs to recall": "Въведете подробности за себе си, за да ги запомнят вашите LLMs",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "Въведете низ за удостоверяване на API (напр. потребителско_име:парола)",
|
"Enter api auth string (e.g. username:password)": "Въведете низ за удостоверяване на API (напр. потребителско_име:парола)",
|
||||||
"Enter Application DN": "Въведете DN на приложението",
|
"Enter Application DN": "Въведете DN на приложението",
|
||||||
"Enter Application DN Password": "Въведете парола за DN на приложението",
|
"Enter Application DN Password": "Въведете парола за DN на приложението",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "Експортиране на чат (.json)",
|
"Export chat (.json)": "Експортиране на чат (.json)",
|
||||||
"Export Chats": "Експортване на чатове",
|
"Export Chats": "Експортване на чатове",
|
||||||
"Export Config to JSON File": "Експортиране на конфигурацията в JSON файл",
|
"Export Config to JSON File": "Експортиране на конфигурацията в JSON файл",
|
||||||
"Export Functions": "Експортиране на функции",
|
|
||||||
"Export Models": "Експортиране на модели",
|
|
||||||
"Export Presets": "Експортиране на предварителни настройки",
|
"Export Presets": "Експортиране на предварителни настройки",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "Експортване на промптове",
|
|
||||||
"Export to CSV": "Експортиране в CSV",
|
"Export to CSV": "Експортиране в CSV",
|
||||||
"Export Tools": "Експортиране на инструменти",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
|
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "Неуспешно запазване на разговора",
|
"Failed to save conversation": "Неуспешно запазване на разговора",
|
||||||
"Failed to save models configuration": "Неуспешно запазване на конфигурацията на моделите",
|
"Failed to save models configuration": "Неуспешно запазване на конфигурацията на моделите",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "Фокусиране на чат вход",
|
"Focus chat input": "Фокусиране на чат вход",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "Папката е изтрита успешно",
|
"Folder deleted successfully": "Папката е изтрита успешно",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "Тактилна обратна връзка",
|
"Haptic Feedback": "Тактилна обратна връзка",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "Здравей, {{name}}",
|
"Hello, {{name}}": "Здравей, {{name}}",
|
||||||
"Help": "Помощ",
|
"Help": "Помощ",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Импортване на чатове",
|
"Import Chats": "Импортване на чатове",
|
||||||
"Import Config from JSON File": "Импортиране на конфигурация от JSON файл",
|
"Import Config from JSON File": "Импортиране на конфигурация от JSON файл",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "Импортиране на функции",
|
|
||||||
"Import Models": "Импортиране на модели",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "Импортиране на предварителни настройки",
|
"Import Presets": "Импортиране на предварителни настройки",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "Импортване на промптове",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "Импортиране на инструменти",
|
|
||||||
"Important Update": "Важна актуализация",
|
"Important Update": "Важна актуализация",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "Включи",
|
"Include": "Включи",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "Невалиден формат на файла.",
|
"Invalid file format.": "Невалиден формат на файла.",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "Невалиден таг",
|
"Invalid Tag": "Невалиден таг",
|
||||||
"is typing...": "пише...",
|
"is typing...": "пише...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Нов чат",
|
"New Chat": "Нов чат",
|
||||||
"New Folder": "Нова папка",
|
"New Folder": "Нова папка",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "Нова бележка",
|
"New Note": "Нова бележка",
|
||||||
"New Password": "Нова парола",
|
"New Password": "Нова парола",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "нов-канал",
|
"new-channel": "нов-канал",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "Няма налично разстояние",
|
"No distance available": "Няма налично разстояние",
|
||||||
"No feedbacks found": "Не са намерени обратни връзки",
|
"No feedbacks found": "Не са намерени обратни връзки",
|
||||||
"No file selected": "Не е избран файл",
|
"No file selected": "Не е избран файл",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "Няма групи с достъп, добавете група, за да предоставите достъп",
|
"No groups with access, add a group to grant access": "Няма групи с достъп, добавете група, за да предоставите достъп",
|
||||||
"No HTML, CSS, or JavaScript content found.": "Не е намерено HTML, CSS или JavaScript съдържание.",
|
"No HTML, CSS, or JavaScript content found.": "Не е намерено HTML, CSS или JavaScript съдържание.",
|
||||||
"No inference engine with management support found": "Не е намерен механизъм за извод с поддръжка на управлението",
|
"No inference engine with management support found": "Не е намерен механизъм за извод с поддръжка на управлението",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "Няма избрани модели",
|
"No models selected": "Няма избрани модели",
|
||||||
"No Notes": "Няма бележки",
|
"No Notes": "Няма бележки",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "Няма намерени резултати",
|
"No results": "Няма намерени резултати",
|
||||||
"No results found": "Няма намерени резултати",
|
"No results found": "Няма намерени резултати",
|
||||||
"No search query generated": "Не е генерирана заявка за търсене",
|
"No search query generated": "Не е генерирана заявка за търсене",
|
||||||
"No source available": "Няма наличен източник",
|
"No source available": "Няма наличен източник",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "Няма предложени подсказки",
|
"No suggestion prompts": "Няма предложени подсказки",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "Не са намерени потребители.",
|
"No users were found.": "Не са намерени потребители.",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "Няма клапани за актуализиране",
|
"No valves to update": "Няма клапани за актуализиране",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "Публично",
|
"Public": "Публично",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Извади \"{{searchValue}}\" от Ollama.com",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Извади \"{{searchValue}}\" от Ollama.com",
|
||||||
"Pull a model from Ollama.com": "Издърпайте модела от Ollama.com",
|
"Pull a model from Ollama.com": "Издърпайте модела от Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "Промпт за генериране на запитвания",
|
"Query Generation Prompt": "Промпт за генериране на запитвания",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1372,6 +1383,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "Изберете знание",
|
"Select Knowledge": "Изберете знание",
|
||||||
"Select only one model to call": "Изберете само един модел за извикване",
|
"Select only one model to call": "Изберете само един модел за извикване",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "Избраният(те) модел(и) не поддържа въвеждане на изображения",
|
"Selected model(s) do not support image inputs": "Избраният(те) модел(и) не поддържа въвеждане на изображения",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "Изпрати",
|
"Send": "Изпрати",
|
||||||
|
|
@ -1412,6 +1424,7 @@
|
||||||
"Share Chat": "Подели Чат",
|
"Share Chat": "Подели Чат",
|
||||||
"Share to Open WebUI Community": "Споделете с OpenWebUI Общността",
|
"Share to Open WebUI Community": "Споделете с OpenWebUI Общността",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "Права за споделяне",
|
"Sharing Permissions": "Права за споделяне",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "Покажи",
|
"Show": "Покажи",
|
||||||
|
|
@ -1479,6 +1492,7 @@
|
||||||
"System Instructions": "Системни инструкции",
|
"System Instructions": "Системни инструкции",
|
||||||
"System Prompt": "Системен Промпт",
|
"System Prompt": "Системен Промпт",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "Тагове",
|
"Tags": "Тагове",
|
||||||
"Tags Generation": "Генериране на тагове",
|
"Tags Generation": "Генериране на тагове",
|
||||||
"Tags Generation Prompt": "Промпт за генериране на тагове",
|
"Tags Generation Prompt": "Промпт за генериране на тагове",
|
||||||
|
|
@ -1590,6 +1604,7 @@
|
||||||
"Transformers": "Трансформатори",
|
"Transformers": "Трансформатори",
|
||||||
"Trouble accessing Ollama?": "Проблеми с достъпа до Ollama?",
|
"Trouble accessing Ollama?": "Проблеми с достъпа до Ollama?",
|
||||||
"Trust Proxy Environment": "",
|
"Trust Proxy Environment": "",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "TTS Модел",
|
"TTS Model": "TTS Модел",
|
||||||
"TTS Settings": "TTS Настройки",
|
"TTS Settings": "TTS Настройки",
|
||||||
|
|
@ -1626,6 +1641,7 @@
|
||||||
"Upload directory": "Качване на директория",
|
"Upload directory": "Качване на директория",
|
||||||
"Upload files": "Качване на файлове",
|
"Upload files": "Качване на файлове",
|
||||||
"Upload Files": "Качване на файлове",
|
"Upload Files": "Качване на файлове",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "Качване на конвейер",
|
"Upload Pipeline": "Качване на конвейер",
|
||||||
"Upload Progress": "Прогрес на качването",
|
"Upload Progress": "Прогрес на качването",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "",
|
"Add User Group": "",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "এই সেটিংগুলো পরিবর্তন করলে তা সব ইউজারের উপরেই প্রয়োগ করা হবে",
|
"Adjusting these settings will apply changes universally to all users.": "এই সেটিংগুলো পরিবর্তন করলে তা সব ইউজারের উপরেই প্রয়োগ করা হবে",
|
||||||
"admin": "এডমিন",
|
"admin": "এডমিন",
|
||||||
"Admin": "",
|
"Admin": "",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "",
|
"Create Group": "",
|
||||||
"Create Knowledge": "",
|
"Create Knowledge": "",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "একটি নতুন কী তৈরি করুন",
|
"Create new key": "একটি নতুন কী তৈরি করুন",
|
||||||
"Create new secret key": "একটি নতুন সিক্রেট কী তৈরি করুন",
|
"Create new secret key": "একটি নতুন সিক্রেট কী তৈরি করুন",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "নির্মানকাল",
|
"Created at": "নির্মানকাল",
|
||||||
"Created At": "নির্মানকাল",
|
"Created At": "নির্মানকাল",
|
||||||
"Created by": "",
|
"Created by": "",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "",
|
"CSV Import": "",
|
||||||
"Ctrl+Enter to Send": "",
|
"Ctrl+Enter to Send": "",
|
||||||
"Current Model": "বর্তমান মডেল",
|
"Current Model": "বর্তমান মডেল",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "",
|
"Delete function?": "",
|
||||||
"Delete Message": "",
|
"Delete Message": "",
|
||||||
"Delete message?": "",
|
"Delete message?": "",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "",
|
"Delete prompt?": "",
|
||||||
"delete this link": "এই লিংক মুছে ফেলুন",
|
"delete this link": "এই লিংক মুছে ফেলুন",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "আপনার এলএলএমগুলি স্মরণ করার জন্য নিজের সম্পর্কে একটি বিশদ লিখুন",
|
"Enter a detail about yourself for your LLMs to recall": "আপনার এলএলএমগুলি স্মরণ করার জন্য নিজের সম্পর্কে একটি বিশদ লিখুন",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "",
|
"Enter api auth string (e.g. username:password)": "",
|
||||||
"Enter Application DN": "",
|
"Enter Application DN": "",
|
||||||
"Enter Application DN Password": "",
|
"Enter Application DN Password": "",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "",
|
"Export chat (.json)": "",
|
||||||
"Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন",
|
"Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন",
|
||||||
"Export Config to JSON File": "",
|
"Export Config to JSON File": "",
|
||||||
"Export Functions": "",
|
|
||||||
"Export Models": "রপ্তানি মডেল",
|
|
||||||
"Export Presets": "",
|
"Export Presets": "",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "প্রম্পটগুলো একপোর্ট করুন",
|
|
||||||
"Export to CSV": "",
|
"Export to CSV": "",
|
||||||
"Export Tools": "",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
|
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "কথোপকথন সংরক্ষণ করতে ব্যর্থ",
|
"Failed to save conversation": "কথোপকথন সংরক্ষণ করতে ব্যর্থ",
|
||||||
"Failed to save models configuration": "",
|
"Failed to save models configuration": "",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "চ্যাট ইনপুট ফোকাস করুন",
|
"Focus chat input": "চ্যাট ইনপুট ফোকাস করুন",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "",
|
"Folder deleted successfully": "",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "",
|
"Haptic Feedback": "",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "হ্যালো, {{name}}",
|
"Hello, {{name}}": "হ্যালো, {{name}}",
|
||||||
"Help": "সহায়তা",
|
"Help": "সহায়তা",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "চ্যাটগুলি ইমপোর্ট করুন",
|
"Import Chats": "চ্যাটগুলি ইমপোর্ট করুন",
|
||||||
"Import Config from JSON File": "",
|
"Import Config from JSON File": "",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "",
|
|
||||||
"Import Models": "মডেল আমদানি করুন",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "",
|
"Import Presets": "",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "প্রম্পটগুলো ইমপোর্ট করুন",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "",
|
|
||||||
"Important Update": "গুরুত্বপূর্ণ আপডেট",
|
"Important Update": "গুরুত্বপূর্ণ আপডেট",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "",
|
"Include": "",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "",
|
"Invalid file format.": "",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "অবৈধ ট্যাগ",
|
"Invalid Tag": "অবৈধ ট্যাগ",
|
||||||
"is typing...": "",
|
"is typing...": "",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "নতুন চ্যাট",
|
"New Chat": "নতুন চ্যাট",
|
||||||
"New Folder": "",
|
"New Folder": "",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "নতুন পাসওয়ার্ড",
|
"New Password": "নতুন পাসওয়ার্ড",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "",
|
"new-channel": "",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "",
|
"No distance available": "",
|
||||||
"No feedbacks found": "",
|
"No feedbacks found": "",
|
||||||
"No file selected": "",
|
"No file selected": "",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "",
|
"No groups with access, add a group to grant access": "",
|
||||||
"No HTML, CSS, or JavaScript content found.": "",
|
"No HTML, CSS, or JavaScript content found.": "",
|
||||||
"No inference engine with management support found": "",
|
"No inference engine with management support found": "",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "",
|
"No models selected": "",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "কোন ফলাফল পাওয়া যায়নি",
|
"No results": "কোন ফলাফল পাওয়া যায়নি",
|
||||||
"No results found": "কোন ফলাফল পাওয়া যায়নি",
|
"No results found": "কোন ফলাফল পাওয়া যায়নি",
|
||||||
"No search query generated": "কোনও অনুসন্ধান ক্যোয়ারী উত্পন্ন হয়নি",
|
"No search query generated": "কোনও অনুসন্ধান ক্যোয়ারী উত্পন্ন হয়নি",
|
||||||
"No source available": "কোন উৎস পাওয়া যায়নি",
|
"No source available": "কোন উৎস পাওয়া যায়নি",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "কোনো প্রস্তাবিত প্রম্পট নেই",
|
"No suggestion prompts": "কোনো প্রস্তাবিত প্রম্পট নেই",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "",
|
"No users were found.": "",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "",
|
"No valves to update": "",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "",
|
"Public": "",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com থেকে \"{{searchValue}}\" টানুন",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com থেকে \"{{searchValue}}\" টানুন",
|
||||||
"Pull a model from Ollama.com": "Ollama.com থেকে একটি টেনে আনুন আনুন",
|
"Pull a model from Ollama.com": "Ollama.com থেকে একটি টেনে আনুন আনুন",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "",
|
"Query Generation Prompt": "",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1372,6 +1383,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "",
|
"Select Knowledge": "",
|
||||||
"Select only one model to call": "",
|
"Select only one model to call": "",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "নির্বাচিত মডেল(গুলি) চিত্র ইনপুট সমর্থন করে না",
|
"Selected model(s) do not support image inputs": "নির্বাচিত মডেল(গুলি) চিত্র ইনপুট সমর্থন করে না",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "পাঠান",
|
"Send": "পাঠান",
|
||||||
|
|
@ -1412,6 +1424,7 @@
|
||||||
"Share Chat": "চ্যাট শেয়ার করুন",
|
"Share Chat": "চ্যাট শেয়ার করুন",
|
||||||
"Share to Open WebUI Community": "OpenWebUI কমিউনিটিতে শেয়ার করুন",
|
"Share to Open WebUI Community": "OpenWebUI কমিউনিটিতে শেয়ার করুন",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "",
|
"Sharing Permissions": "",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "দেখান",
|
"Show": "দেখান",
|
||||||
|
|
@ -1479,6 +1492,7 @@
|
||||||
"System Instructions": "",
|
"System Instructions": "",
|
||||||
"System Prompt": "সিস্টেম প্রম্পট",
|
"System Prompt": "সিস্টেম প্রম্পট",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "",
|
"Tags": "",
|
||||||
"Tags Generation": "",
|
"Tags Generation": "",
|
||||||
"Tags Generation Prompt": "",
|
"Tags Generation Prompt": "",
|
||||||
|
|
@ -1590,6 +1604,7 @@
|
||||||
"Transformers": "",
|
"Transformers": "",
|
||||||
"Trouble accessing Ollama?": "Ollama এক্সেস করতে সমস্যা হচ্ছে?",
|
"Trouble accessing Ollama?": "Ollama এক্সেস করতে সমস্যা হচ্ছে?",
|
||||||
"Trust Proxy Environment": "",
|
"Trust Proxy Environment": "",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "",
|
"TTS Model": "",
|
||||||
"TTS Settings": "TTS সেটিংসমূহ",
|
"TTS Settings": "TTS সেটিংসমূহ",
|
||||||
|
|
@ -1626,6 +1641,7 @@
|
||||||
"Upload directory": "",
|
"Upload directory": "",
|
||||||
"Upload files": "",
|
"Upload files": "",
|
||||||
"Upload Files": "ফাইল আপলোড করুন",
|
"Upload Files": "ফাইল আপলোড করুন",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "",
|
"Upload Pipeline": "",
|
||||||
"Upload Progress": "আপলোড হচ্ছে",
|
"Upload Progress": "আপলোড হচ্ছে",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "བེད་སྤྱོད་མཁན་ཚོགས་པ་སྣོན་པ།",
|
"Add User Group": "བེད་སྤྱོད་མཁན་ཚོགས་པ་སྣོན་པ།",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "སྒྲིག་འགོད་འདི་དག་ལེགས་སྒྲིག་བྱས་ན་བེད་སྤྱོད་མཁན་ཡོངས་ལ་འགྱུར་བ་དེ་བཀོལ་སྤྱོད་བྱེད་ངེས།",
|
"Adjusting these settings will apply changes universally to all users.": "སྒྲིག་འགོད་འདི་དག་ལེགས་སྒྲིག་བྱས་ན་བེད་སྤྱོད་མཁན་ཡོངས་ལ་འགྱུར་བ་དེ་བཀོལ་སྤྱོད་བྱེད་ངེས།",
|
||||||
"admin": "དོ་དམ་པ།",
|
"admin": "དོ་དམ་པ།",
|
||||||
"Admin": "དོ་དམ་པ།",
|
"Admin": "དོ་དམ་པ།",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "ཚོགས་པ་གསར་བཟོ།",
|
"Create Group": "ཚོགས་པ་གསར་བཟོ།",
|
||||||
"Create Knowledge": "ཤེས་བྱ་གསར་བཟོ།",
|
"Create Knowledge": "ཤེས་བྱ་གསར་བཟོ།",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
"Create new key": "ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
||||||
"Create new secret key": "གསང་བའི་ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
"Create new secret key": "གསང་བའི་ལྡེ་མིག་གསར་པ་བཟོ་བ།",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "གསར་བཟོ་བྱེད་དུས།",
|
"Created at": "གསར་བཟོ་བྱེད་དུས།",
|
||||||
"Created At": "གསར་བཟོ་བྱེད་དུས།",
|
"Created At": "གསར་བཟོ་བྱེད་དུས།",
|
||||||
"Created by": "གསར་བཟོ་བྱེད་མཁན།",
|
"Created by": "གསར་བཟོ་བྱེད་མཁན།",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "CSV ནང་འདྲེན།",
|
"CSV Import": "CSV ནང་འདྲེན།",
|
||||||
"Ctrl+Enter to Send": "Ctrl+Enter གཏོང་བ།",
|
"Ctrl+Enter to Send": "Ctrl+Enter གཏོང་བ།",
|
||||||
"Current Model": "ད་ལྟའི་དཔེ་དབྱིབས།",
|
"Current Model": "ད་ལྟའི་དཔེ་དབྱིབས།",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "ལས་འགན་བསུབ་པ།?",
|
"Delete function?": "ལས་འགན་བསུབ་པ།?",
|
||||||
"Delete Message": "འཕྲིན་བསུབ་པ།",
|
"Delete Message": "འཕྲིན་བསུབ་པ།",
|
||||||
"Delete message?": "འཕྲིན་བསུབ་པ།?",
|
"Delete message?": "འཕྲིན་བསུབ་པ།?",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "འགུལ་སློང་བསུབ་པ།?",
|
"Delete prompt?": "འགུལ་སློང་བསུབ་པ།?",
|
||||||
"delete this link": "སྦྲེལ་ཐག་འདི་བསུབ་པ།",
|
"delete this link": "སྦྲེལ་ཐག་འདི་བསུབ་པ།",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "ཁྱེད་ཀྱི་ LLMs ཡིས་ཕྱིར་དྲན་ཆེད་དུ་ཁྱེད་རང་གི་སྐོར་གྱི་ཞིབ་ཕྲ་ཞིག་འཇུག་པ།",
|
"Enter a detail about yourself for your LLMs to recall": "ཁྱེད་ཀྱི་ LLMs ཡིས་ཕྱིར་དྲན་ཆེད་དུ་ཁྱེད་རང་གི་སྐོར་གྱི་ཞིབ་ཕྲ་ཞིག་འཇུག་པ།",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "api auth ཡིག་ཕྲེང་འཇུག་པ། (དཔེར་ན། username:password)",
|
"Enter api auth string (e.g. username:password)": "api auth ཡིག་ཕྲེང་འཇུག་པ། (དཔེར་ན། username:password)",
|
||||||
"Enter Application DN": "Application DN འཇུག་པ།",
|
"Enter Application DN": "Application DN འཇུག་པ།",
|
||||||
"Enter Application DN Password": "Application DN གསང་གྲངས་འཇུག་པ།",
|
"Enter Application DN Password": "Application DN གསང་གྲངས་འཇུག་པ།",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "ཁ་བརྡ་ཕྱིར་གཏོང་ (.json)",
|
"Export chat (.json)": "ཁ་བརྡ་ཕྱིར་གཏོང་ (.json)",
|
||||||
"Export Chats": "ཁ་བརྡ་ཕྱིར་གཏོང་།",
|
"Export Chats": "ཁ་བརྡ་ཕྱིར་གཏོང་།",
|
||||||
"Export Config to JSON File": "སྒྲིག་འགོད་ JSON ཡིག་ཆར་ཕྱིར་གཏོང་།",
|
"Export Config to JSON File": "སྒྲིག་འགོད་ JSON ཡིག་ཆར་ཕྱིར་གཏོང་།",
|
||||||
"Export Functions": "ལས་འགན་ཕྱིར་གཏོང་།",
|
|
||||||
"Export Models": "དཔེ་དབྱིབས་ཕྱིར་གཏོང་།",
|
|
||||||
"Export Presets": "སྔོན་སྒྲིག་ཕྱིར་གཏོང་།",
|
"Export Presets": "སྔོན་སྒྲིག་ཕྱིར་གཏོང་།",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "འགུལ་སློང་ཕྱིར་གཏོང་།",
|
|
||||||
"Export to CSV": "CSV ལ་ཕྱིར་གཏོང་།",
|
"Export to CSV": "CSV ལ་ཕྱིར་གཏོང་།",
|
||||||
"Export Tools": "ལག་ཆ་ཕྱིར་གཏོང་།",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "ཕྱི་རོལ།",
|
"External": "ཕྱི་རོལ།",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "སྦྱར་སྡེར་གྱི་ནང་དོན་ཀློག་མ་ཐུབ།",
|
"Failed to read clipboard contents": "སྦྱར་སྡེར་གྱི་ནང་དོན་ཀློག་མ་ཐུབ།",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "གླེང་མོལ་ཉར་ཚགས་བྱེད་མ་ཐུབ།",
|
"Failed to save conversation": "གླེང་མོལ་ཉར་ཚགས་བྱེད་མ་ཐུབ།",
|
||||||
"Failed to save models configuration": "དཔེ་དབྱིབས་སྒྲིག་འགོད་ཉར་ཚགས་བྱེད་མ་ཐུབ།",
|
"Failed to save models configuration": "དཔེ་དབྱིབས་སྒྲིག་འགོད་ཉར་ཚགས་བྱེད་མ་ཐུབ།",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "ཁ་བརྡའི་ནང་འཇུག་ལ་དམིགས་པ།",
|
"Focus chat input": "ཁ་བརྡའི་ནང་འཇུག་ལ་དམིགས་པ།",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "ཡིག་སྣོད་ལེགས་པར་བསུབས་ཟིན།",
|
"Folder deleted successfully": "ཡིག་སྣོད་ལེགས་པར་བསུབས་ཟིན།",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "འདར་འཕྲུལ་གྱི་བསམ་འཆར།",
|
"Haptic Feedback": "འདར་འཕྲུལ་གྱི་བསམ་འཆར།",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "བཀྲ་ཤིས་བདེ་ལེགས། {{name}}",
|
"Hello, {{name}}": "བཀྲ་ཤིས་བདེ་ལེགས། {{name}}",
|
||||||
"Help": "རོགས་རམ།",
|
"Help": "རོགས་རམ།",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "ཁ་བརྡ་ནང་འདྲེན།",
|
"Import Chats": "ཁ་བརྡ་ནང་འདྲེན།",
|
||||||
"Import Config from JSON File": "JSON ཡིག་ཆ་ནས་སྒྲིག་འགོད་ནང་འདྲེན།",
|
"Import Config from JSON File": "JSON ཡིག་ཆ་ནས་སྒྲིག་འགོད་ནང་འདྲེན།",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "ལས་འགན་ནང་འདྲེན།",
|
|
||||||
"Import Models": "དཔེ་དབྱིབས་ནང་འདྲེན།",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "སྔོན་སྒྲིག་ནང་འདྲེན།",
|
"Import Presets": "སྔོན་སྒྲིག་ནང་འདྲེན།",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "འགུལ་སློང་ནང་འདྲེན།",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "ལག་ཆ་ནང་འདྲེན།",
|
|
||||||
"Important Update": "གལ་ཆེ་པའི་གསར་སྒྱུར་",
|
"Important Update": "གལ་ཆེ་པའི་གསར་སྒྱུར་",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "ཚུད་པ།",
|
"Include": "ཚུད་པ།",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "ཡིག་ཆའི་བཀོད་པ་ནུས་མེད།",
|
"Invalid file format.": "ཡིག་ཆའི་བཀོད་པ་ནུས་མེད།",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "རྟགས་ནུས་མེད།",
|
"Invalid Tag": "རྟགས་ནུས་མེད།",
|
||||||
"is typing...": "ཡིག་འབྲུ་རྒྱག་བཞིན་པ།...",
|
"is typing...": "ཡིག་འབྲུ་རྒྱག་བཞིན་པ།...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "ཁ་བརྡ་གསར་པ།",
|
"New Chat": "ཁ་བརྡ་གསར་པ།",
|
||||||
"New Folder": "ཡིག་སྣོད་གསར་པ།",
|
"New Folder": "ཡིག་སྣོད་གསར་པ།",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "གསང་གྲངས་གསར་པ།",
|
"New Password": "གསང་གྲངས་གསར་པ།",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "བགྲོ་གླེང་གསར་པ།",
|
"new-channel": "བགྲོ་གླེང་གསར་པ།",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "ཐག་རིང་ཚད་མེད།",
|
"No distance available": "ཐག་རིང་ཚད་མེད།",
|
||||||
"No feedbacks found": "བསམ་འཆར་མ་རྙེད།",
|
"No feedbacks found": "བསམ་འཆར་མ་རྙེད།",
|
||||||
"No file selected": "ཡིག་ཆ་གདམ་ག་མ་བྱས།",
|
"No file selected": "ཡིག་ཆ་གདམ་ག་མ་བྱས།",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "འཛུལ་སྤྱོད་ཡོད་པའི་ཚོགས་པ་མེད། འཛུལ་སྤྱོད་སྤྲོད་པར་ཚོགས་པ་ཞིག་སྣོན་པ།",
|
"No groups with access, add a group to grant access": "འཛུལ་སྤྱོད་ཡོད་པའི་ཚོགས་པ་མེད། འཛུལ་སྤྱོད་སྤྲོད་པར་ཚོགས་པ་ཞིག་སྣོན་པ།",
|
||||||
"No HTML, CSS, or JavaScript content found.": "HTML, CSS, ཡང་ན་ JavaScript གི་ནང་དོན་མ་རྙེད།",
|
"No HTML, CSS, or JavaScript content found.": "HTML, CSS, ཡང་ན་ JavaScript གི་ནང་དོན་མ་རྙེད།",
|
||||||
"No inference engine with management support found": "དོ་དམ་རྒྱབ་སྐྱོར་ཡོད་པའི་དཔོག་རྩིས་འཕྲུལ་འཁོར་མ་རྙེད།",
|
"No inference engine with management support found": "དོ་དམ་རྒྱབ་སྐྱོར་ཡོད་པའི་དཔོག་རྩིས་འཕྲུལ་འཁོར་མ་རྙེད།",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "དཔེ་དབྱིབས་གདམ་ག་མ་བྱས།",
|
"No models selected": "དཔེ་དབྱིབས་གདམ་ག་མ་བྱས།",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "འབྲས་བུ་མ་རྙེད།",
|
"No results": "འབྲས་བུ་མ་རྙེད།",
|
||||||
"No results found": "འབྲས་བུ་མ་རྙེད།",
|
"No results found": "འབྲས་བུ་མ་རྙེད།",
|
||||||
"No search query generated": "འཚོལ་བཤེར་འདྲི་བ་བཟོས་མེད།",
|
"No search query generated": "འཚོལ་བཤེར་འདྲི་བ་བཟོས་མེད།",
|
||||||
"No source available": "འབྱུང་ཁུངས་མེད།",
|
"No source available": "འབྱུང་ཁུངས་མེད།",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "གསལ་འདེབས་མེད།",
|
"No suggestion prompts": "གསལ་འདེབས་མེད།",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "བེད་སྤྱོད་མཁན་མ་རྙེད།",
|
"No users were found.": "བེད་སྤྱོད་མཁན་མ་རྙེད།",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "གསར་སྒྱུར་བྱེད་རྒྱུའི་ Valve མེད།",
|
"No valves to update": "གསར་སྒྱུར་བྱེད་རྒྱུའི་ Valve མེད།",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "སྤྱི་སྤྱོད།",
|
"Public": "སྤྱི་སྤྱོད།",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com ནས་ \"{{searchValue}}\" འཐེན་པ།",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Ollama.com ནས་ \"{{searchValue}}\" འཐེན་པ།",
|
||||||
"Pull a model from Ollama.com": "Ollama.com ནས་དཔེ་དབྱིབས་ཤིག་འཐེན་པ།",
|
"Pull a model from Ollama.com": "Ollama.com ནས་དཔེ་དབྱིབས་ཤིག་འཐེན་པ།",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "འདྲི་བ་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
|
"Query Generation Prompt": "འདྲི་བ་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1371,6 +1382,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "ཤེས་བྱ་གདམ་པ།",
|
"Select Knowledge": "ཤེས་བྱ་གདམ་པ།",
|
||||||
"Select only one model to call": "འབོད་པར་དཔེ་དབྱིབས་གཅིག་ཁོ་ན་གདམ་པ།",
|
"Select only one model to call": "འབོད་པར་དཔེ་དབྱིབས་གཅིག་ཁོ་ན་གདམ་པ།",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "གདམ་ཟིན་པའི་དཔེ་དབྱིབས་(ཚོ)ས་པར་གྱི་ནང་འཇུག་ལ་རྒྱབ་སྐྱོར་མི་བྱེད།",
|
"Selected model(s) do not support image inputs": "གདམ་ཟིན་པའི་དཔེ་དབྱིབས་(ཚོ)ས་པར་གྱི་ནང་འཇུག་ལ་རྒྱབ་སྐྱོར་མི་བྱེད།",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "གཏོང་བ།",
|
"Send": "གཏོང་བ།",
|
||||||
|
|
@ -1411,6 +1423,7 @@
|
||||||
"Share Chat": "ཁ་བརྡ་མཉམ་སྤྱོད།",
|
"Share Chat": "ཁ་བརྡ་མཉམ་སྤྱོད།",
|
||||||
"Share to Open WebUI Community": "Open WebUI སྤྱི་ཚོགས་ལ་མཉམ་སྤྱོད།",
|
"Share to Open WebUI Community": "Open WebUI སྤྱི་ཚོགས་ལ་མཉམ་སྤྱོད།",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "མཉམ་སྤྱོད་དབང་ཚད།",
|
"Sharing Permissions": "མཉམ་སྤྱོད་དབང་ཚད།",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "སྟོན་པ།",
|
"Show": "སྟོན་པ།",
|
||||||
|
|
@ -1478,6 +1491,7 @@
|
||||||
"System Instructions": "མ་ལག་གི་ལམ་སྟོན།",
|
"System Instructions": "མ་ལག་གི་ལམ་སྟོན།",
|
||||||
"System Prompt": "མ་ལག་གི་འགུལ་སློང་།",
|
"System Prompt": "མ་ལག་གི་འགུལ་སློང་།",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "རྟགས།",
|
"Tags": "རྟགས།",
|
||||||
"Tags Generation": "རྟགས་བཟོ་སྐྲུན།",
|
"Tags Generation": "རྟགས་བཟོ་སྐྲུན།",
|
||||||
"Tags Generation Prompt": "རྟགས་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
|
"Tags Generation Prompt": "རྟགས་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
|
||||||
|
|
@ -1589,6 +1603,7 @@
|
||||||
"Transformers": "Transformers",
|
"Transformers": "Transformers",
|
||||||
"Trouble accessing Ollama?": "Ollama འཛུལ་སྤྱོད་སྐབས་དཀའ་ངལ་འཕྲད་དམ།",
|
"Trouble accessing Ollama?": "Ollama འཛུལ་སྤྱོད་སྐབས་དཀའ་ངལ་འཕྲད་དམ།",
|
||||||
"Trust Proxy Environment": "Proxy ཁོར་ཡུག་ལ་ཡིད་ཆེས།",
|
"Trust Proxy Environment": "Proxy ཁོར་ཡུག་ལ་ཡིད་ཆེས།",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "TTS དཔེ་དབྱིབས།",
|
"TTS Model": "TTS དཔེ་དབྱིབས།",
|
||||||
"TTS Settings": "TTS སྒྲིག་འགོད།",
|
"TTS Settings": "TTS སྒྲིག་འགོད།",
|
||||||
|
|
@ -1625,6 +1640,7 @@
|
||||||
"Upload directory": "སྤར་བའི་ཐོ་འཚོལ།",
|
"Upload directory": "སྤར་བའི་ཐོ་འཚོལ།",
|
||||||
"Upload files": "ཡིག་ཆ་སྤར་བ།",
|
"Upload files": "ཡིག་ཆ་སྤར་བ།",
|
||||||
"Upload Files": "ཡིག་ཆ་སྤར་བ།",
|
"Upload Files": "ཡིག་ཆ་སྤར་བ།",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "རྒྱུ་ལམ་སྤར་བ།",
|
"Upload Pipeline": "རྒྱུ་ལམ་སྤར་བ།",
|
||||||
"Upload Progress": "སྤར་བའི་འཕེལ་རིམ།",
|
"Upload Progress": "སྤར་བའི་འཕེལ་རིམ།",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "",
|
"Add User Group": "",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "Podešavanje će se primijeniti univerzalno na sve korisnike.",
|
"Adjusting these settings will apply changes universally to all users.": "Podešavanje će se primijeniti univerzalno na sve korisnike.",
|
||||||
"admin": "administrator",
|
"admin": "administrator",
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "",
|
"Create Group": "",
|
||||||
"Create Knowledge": "",
|
"Create Knowledge": "",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "Stvori novi ključ",
|
"Create new key": "Stvori novi ključ",
|
||||||
"Create new secret key": "Stvori novi tajni ključ",
|
"Create new secret key": "Stvori novi tajni ključ",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Stvoreno",
|
"Created at": "Stvoreno",
|
||||||
"Created At": "Stvoreno",
|
"Created At": "Stvoreno",
|
||||||
"Created by": "",
|
"Created by": "",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "",
|
"CSV Import": "",
|
||||||
"Ctrl+Enter to Send": "",
|
"Ctrl+Enter to Send": "",
|
||||||
"Current Model": "Trenutni model",
|
"Current Model": "Trenutni model",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "",
|
"Delete function?": "",
|
||||||
"Delete Message": "",
|
"Delete Message": "",
|
||||||
"Delete message?": "",
|
"Delete message?": "",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "",
|
"Delete prompt?": "",
|
||||||
"delete this link": "izbriši ovu vezu",
|
"delete this link": "izbriši ovu vezu",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "Unesite pojedinosti o sebi da bi učitali memoriju u LLM",
|
"Enter a detail about yourself for your LLMs to recall": "Unesite pojedinosti o sebi da bi učitali memoriju u LLM",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "",
|
"Enter api auth string (e.g. username:password)": "",
|
||||||
"Enter Application DN": "",
|
"Enter Application DN": "",
|
||||||
"Enter Application DN Password": "",
|
"Enter Application DN Password": "",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "Izvoz četa (.json)",
|
"Export chat (.json)": "Izvoz četa (.json)",
|
||||||
"Export Chats": "Izvoz razgovora",
|
"Export Chats": "Izvoz razgovora",
|
||||||
"Export Config to JSON File": "",
|
"Export Config to JSON File": "",
|
||||||
"Export Functions": "",
|
|
||||||
"Export Models": "Izvoz modela",
|
|
||||||
"Export Presets": "",
|
"Export Presets": "",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "Izvoz prompta",
|
|
||||||
"Export to CSV": "",
|
"Export to CSV": "",
|
||||||
"Export Tools": "Izvoz alata",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "Neuspješno čitanje sadržaja međuspremnika",
|
"Failed to read clipboard contents": "Neuspješno čitanje sadržaja međuspremnika",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "Neuspješno spremanje razgovora",
|
"Failed to save conversation": "Neuspješno spremanje razgovora",
|
||||||
"Failed to save models configuration": "",
|
"Failed to save models configuration": "",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "Fokusiraj unos razgovora",
|
"Focus chat input": "Fokusiraj unos razgovora",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "",
|
"Folder deleted successfully": "",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "",
|
"Haptic Feedback": "",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "Bok, {{name}}",
|
"Hello, {{name}}": "Bok, {{name}}",
|
||||||
"Help": "Pomoć",
|
"Help": "Pomoć",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Uvoz razgovora",
|
"Import Chats": "Uvoz razgovora",
|
||||||
"Import Config from JSON File": "",
|
"Import Config from JSON File": "",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "",
|
|
||||||
"Import Models": "Uvoz modela",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "",
|
"Import Presets": "",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "Uvoz prompta",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "Uvoz alata",
|
|
||||||
"Important Update": "Važno ažuriranje",
|
"Important Update": "Važno ažuriranje",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "",
|
"Include": "",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "",
|
"Invalid file format.": "",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "Nevažeća oznaka",
|
"Invalid Tag": "Nevažeća oznaka",
|
||||||
"is typing...": "",
|
"is typing...": "",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Novi razgovor",
|
"New Chat": "Novi razgovor",
|
||||||
"New Folder": "",
|
"New Folder": "",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "Nova lozinka",
|
"New Password": "Nova lozinka",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "",
|
"new-channel": "",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "",
|
"No distance available": "",
|
||||||
"No feedbacks found": "",
|
"No feedbacks found": "",
|
||||||
"No file selected": "",
|
"No file selected": "",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "",
|
"No groups with access, add a group to grant access": "",
|
||||||
"No HTML, CSS, or JavaScript content found.": "",
|
"No HTML, CSS, or JavaScript content found.": "",
|
||||||
"No inference engine with management support found": "",
|
"No inference engine with management support found": "",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "",
|
"No models selected": "",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "Nema rezultata",
|
"No results": "Nema rezultata",
|
||||||
"No results found": "Nema rezultata",
|
"No results found": "Nema rezultata",
|
||||||
"No search query generated": "Nije generiran upit za pretraživanje",
|
"No search query generated": "Nije generiran upit za pretraživanje",
|
||||||
"No source available": "Nema dostupnog izvora",
|
"No source available": "Nema dostupnog izvora",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "Nema predloženih prompta",
|
"No suggestion prompts": "Nema predloženih prompta",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "",
|
"No users were found.": "",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "",
|
"No valves to update": "",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "",
|
"Public": "",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Povucite \"{{searchValue}}\" s Ollama.com",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Povucite \"{{searchValue}}\" s Ollama.com",
|
||||||
"Pull a model from Ollama.com": "Povucite model s Ollama.com",
|
"Pull a model from Ollama.com": "Povucite model s Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "",
|
"Query Generation Prompt": "",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1373,6 +1384,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "",
|
"Select Knowledge": "",
|
||||||
"Select only one model to call": "Odaberite samo jedan model za poziv",
|
"Select only one model to call": "Odaberite samo jedan model za poziv",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "Odabrani modeli ne podržavaju unose slika",
|
"Selected model(s) do not support image inputs": "Odabrani modeli ne podržavaju unose slika",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "Pošalji",
|
"Send": "Pošalji",
|
||||||
|
|
@ -1413,6 +1425,7 @@
|
||||||
"Share Chat": "Podijeli razgovor",
|
"Share Chat": "Podijeli razgovor",
|
||||||
"Share to Open WebUI Community": "Podijeli u OpenWebUI zajednici",
|
"Share to Open WebUI Community": "Podijeli u OpenWebUI zajednici",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "",
|
"Sharing Permissions": "",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "Pokaži",
|
"Show": "Pokaži",
|
||||||
|
|
@ -1480,6 +1493,7 @@
|
||||||
"System Instructions": "",
|
"System Instructions": "",
|
||||||
"System Prompt": "Sistemski prompt",
|
"System Prompt": "Sistemski prompt",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "",
|
"Tags": "",
|
||||||
"Tags Generation": "",
|
"Tags Generation": "",
|
||||||
"Tags Generation Prompt": "",
|
"Tags Generation Prompt": "",
|
||||||
|
|
@ -1591,6 +1605,7 @@
|
||||||
"Transformers": "",
|
"Transformers": "",
|
||||||
"Trouble accessing Ollama?": "Problemi s pristupom Ollama?",
|
"Trouble accessing Ollama?": "Problemi s pristupom Ollama?",
|
||||||
"Trust Proxy Environment": "",
|
"Trust Proxy Environment": "",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "TTS model",
|
"TTS Model": "TTS model",
|
||||||
"TTS Settings": "TTS postavke",
|
"TTS Settings": "TTS postavke",
|
||||||
|
|
@ -1627,6 +1642,7 @@
|
||||||
"Upload directory": "",
|
"Upload directory": "",
|
||||||
"Upload files": "",
|
"Upload files": "",
|
||||||
"Upload Files": "Prijenos datoteka",
|
"Upload Files": "Prijenos datoteka",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "Prijenos kanala",
|
"Upload Pipeline": "Prijenos kanala",
|
||||||
"Upload Progress": "Napredak učitavanja",
|
"Upload Progress": "Napredak učitavanja",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "Afegir grup d'usuaris",
|
"Add User Group": "Afegir grup d'usuaris",
|
||||||
"Additional Config": "Configuració addicional",
|
"Additional Config": "Configuració addicional",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "Opcions de configuració addicionals per al marcador. Hauria de ser una cadena JSON amb parelles clau-valor. Per exemple, '{\"key\": \"value\"}'. Les claus compatibles inclouen: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "Opcions de configuració addicionals per al marcador. Hauria de ser una cadena JSON amb parelles clau-valor. Per exemple, '{\"key\": \"value\"}'. Les claus compatibles inclouen: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "Si ajustes aquesta preferència, els canvis s'aplicaran de manera universal a tots els usuaris.",
|
"Adjusting these settings will apply changes universally to all users.": "Si ajustes aquesta preferència, els canvis s'aplicaran de manera universal a tots els usuaris.",
|
||||||
"admin": "administrador",
|
"admin": "administrador",
|
||||||
"Admin": "Administrador",
|
"Admin": "Administrador",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "Crear carpeta",
|
"Create Folder": "Crear carpeta",
|
||||||
"Create Group": "Crear grup",
|
"Create Group": "Crear grup",
|
||||||
"Create Knowledge": "Crear Coneixement",
|
"Create Knowledge": "Crear Coneixement",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "Crear una nova clau",
|
"Create new key": "Crear una nova clau",
|
||||||
"Create new secret key": "Crear una nova clau secreta",
|
"Create new secret key": "Crear una nova clau secreta",
|
||||||
"Create Note": "Crea nota",
|
"Create Note": "Crea nota",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Creat el",
|
"Created at": "Creat el",
|
||||||
"Created At": "Creat el",
|
"Created At": "Creat el",
|
||||||
"Created by": "Creat per",
|
"Created by": "Creat per",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "Importar CSV",
|
"CSV Import": "Importar CSV",
|
||||||
"Ctrl+Enter to Send": "Ctrl+Enter per enviar",
|
"Ctrl+Enter to Send": "Ctrl+Enter per enviar",
|
||||||
"Current Model": "Model actual",
|
"Current Model": "Model actual",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "Eliminar funció?",
|
"Delete function?": "Eliminar funció?",
|
||||||
"Delete Message": "Eliminar el missatge",
|
"Delete Message": "Eliminar el missatge",
|
||||||
"Delete message?": "Eliminar el missatge?",
|
"Delete message?": "Eliminar el missatge?",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "Eliminar la nota?",
|
"Delete note?": "Eliminar la nota?",
|
||||||
"Delete prompt?": "Eliminar indicació?",
|
"Delete prompt?": "Eliminar indicació?",
|
||||||
"delete this link": "Eliminar aquest enllaç",
|
"delete this link": "Eliminar aquest enllaç",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar",
|
"Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "Introdueix un títol per a la finestra de dades d'usuari pendent. Deixa buit per a valor per defecte.",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "Introdueix un títol per a la finestra de dades d'usuari pendent. Deixa buit per a valor per defecte.",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "Introdueix una marca d'aigua per a la resposta. Deixa-ho buit per a cap.",
|
"Enter a watermark for the response. Leave empty for none.": "Introdueix una marca d'aigua per a la resposta. Deixa-ho buit per a cap.",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "Entra la cadena d'autenticació api (p. ex. nom d'usuari:contrasenya)",
|
"Enter api auth string (e.g. username:password)": "Entra la cadena d'autenticació api (p. ex. nom d'usuari:contrasenya)",
|
||||||
"Enter Application DN": "Introdueix el DN d'aplicació",
|
"Enter Application DN": "Introdueix el DN d'aplicació",
|
||||||
"Enter Application DN Password": "Introdueix la contrasenya del DN d'aplicació",
|
"Enter Application DN Password": "Introdueix la contrasenya del DN d'aplicació",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "Exportar el xat (.json)",
|
"Export chat (.json)": "Exportar el xat (.json)",
|
||||||
"Export Chats": "Exportar els xats",
|
"Export Chats": "Exportar els xats",
|
||||||
"Export Config to JSON File": "Exportar la configuració a un arxiu JSON",
|
"Export Config to JSON File": "Exportar la configuració a un arxiu JSON",
|
||||||
"Export Functions": "Exportar funcions",
|
|
||||||
"Export Models": "Exportar els models",
|
|
||||||
"Export Presets": "Exportar les configuracions",
|
"Export Presets": "Exportar les configuracions",
|
||||||
"Export Prompt Suggestions": "Exportar els suggeriments d'indicació",
|
"Export Prompt Suggestions": "Exportar els suggeriments d'indicació",
|
||||||
"Export Prompts": "Exportar les indicacions",
|
|
||||||
"Export to CSV": "Exportar a CSV",
|
"Export to CSV": "Exportar a CSV",
|
||||||
"Export Tools": "Exportar les eines",
|
|
||||||
"Export Users": "Exportar els usuaris",
|
"Export Users": "Exportar els usuaris",
|
||||||
"External": "Extern",
|
"External": "Extern",
|
||||||
"External Document Loader URL required.": "Fa falta la URL per a Document Loader",
|
"External Document Loader URL required.": "Fa falta la URL per a Document Loader",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "No s'ha pogut carregar el contingut del fitxer",
|
"Failed to load file content.": "No s'ha pogut carregar el contingut del fitxer",
|
||||||
"Failed to move chat": "No s'ha pogut moure el xat",
|
"Failed to move chat": "No s'ha pogut moure el xat",
|
||||||
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
|
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "No s'han pogut desar les connexions",
|
"Failed to save connections": "No s'han pogut desar les connexions",
|
||||||
"Failed to save conversation": "No s'ha pogut desar la conversa",
|
"Failed to save conversation": "No s'ha pogut desar la conversa",
|
||||||
"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
|
"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "Clau API de Firecrawl",
|
"Firecrawl API Key": "Clau API de Firecrawl",
|
||||||
"Floating Quick Actions": "Accions ràpides flotants",
|
"Floating Quick Actions": "Accions ràpides flotants",
|
||||||
"Focus chat input": "Estableix el focus a l'entrada del xat",
|
"Focus chat input": "Estableix el focus a l'entrada del xat",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "Imatge del fons de la carpeta",
|
"Folder Background Image": "Imatge del fons de la carpeta",
|
||||||
"Folder deleted successfully": "Carpeta eliminada correctament",
|
"Folder deleted successfully": "Carpeta eliminada correctament",
|
||||||
"Folder Name": "Nom de la carpeta",
|
"Folder Name": "Nom de la carpeta",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "H2",
|
"H2": "H2",
|
||||||
"H3": "H3",
|
"H3": "H3",
|
||||||
"Haptic Feedback": "Retorn hàptic",
|
"Haptic Feedback": "Retorn hàptic",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "Alçada",
|
"Height": "Alçada",
|
||||||
"Hello, {{name}}": "Hola, {{name}}",
|
"Hello, {{name}}": "Hola, {{name}}",
|
||||||
"Help": "Ajuda",
|
"Help": "Ajuda",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Importar xats",
|
"Import Chats": "Importar xats",
|
||||||
"Import Config from JSON File": "Importar la configuració des d'un arxiu JSON",
|
"Import Config from JSON File": "Importar la configuració des d'un arxiu JSON",
|
||||||
"Import From Link": "Importar des d'un enllaç",
|
"Import From Link": "Importar des d'un enllaç",
|
||||||
"Import Functions": "Importar funcions",
|
|
||||||
"Import Models": "Importar models",
|
|
||||||
"Import Notes": "Importar nota",
|
"Import Notes": "Importar nota",
|
||||||
"Import Presets": "Importar configuracions",
|
"Import Presets": "Importar configuracions",
|
||||||
"Import Prompt Suggestions": "Importar suggeriments d'indicacions",
|
"Import Prompt Suggestions": "Importar suggeriments d'indicacions",
|
||||||
"Import Prompts": "Importar indicacions",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "Importar eines",
|
|
||||||
"Important Update": "Actualització important",
|
"Important Update": "Actualització important",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "Per forçar l'OCR, cal activar l'OCR.",
|
"In order to force OCR, performing OCR must be enabled.": "Per forçar l'OCR, cal activar l'OCR.",
|
||||||
"Include": "Incloure",
|
"Include": "Incloure",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "Format d'arxiu no vàlid.",
|
"Invalid file format.": "Format d'arxiu no vàlid.",
|
||||||
"Invalid JSON file": "Arxiu JSON no vàlid",
|
"Invalid JSON file": "Arxiu JSON no vàlid",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "Arxiu JSON de Workflow ComfyUI no vàlid.",
|
"Invalid JSON format for ComfyUI Workflow.": "Arxiu JSON de Workflow ComfyUI no vàlid.",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "Format JSON no vàlid a la configuració addicional",
|
"Invalid JSON format in Additional Config": "Format JSON no vàlid a la configuració addicional",
|
||||||
"Invalid Tag": "Etiqueta no vàlida",
|
"Invalid Tag": "Etiqueta no vàlida",
|
||||||
"is typing...": "està escrivint...",
|
"is typing...": "està escrivint...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Nou xat",
|
"New Chat": "Nou xat",
|
||||||
"New Folder": "Nova carpeta",
|
"New Folder": "Nova carpeta",
|
||||||
"New Function": "Nova funció",
|
"New Function": "Nova funció",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "Nova nota",
|
"New Note": "Nova nota",
|
||||||
"New Password": "Nova contrasenya",
|
"New Password": "Nova contrasenya",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "Nova eina",
|
"New Tool": "Nova eina",
|
||||||
"new-channel": "nou-canal",
|
"new-channel": "nou-canal",
|
||||||
"Next message": "Missatge següent",
|
"Next message": "Missatge següent",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "No hi ha distància disponible",
|
"No distance available": "No hi ha distància disponible",
|
||||||
"No feedbacks found": "No s'han trobat comentaris",
|
"No feedbacks found": "No s'han trobat comentaris",
|
||||||
"No file selected": "No s'ha escollit cap fitxer",
|
"No file selected": "No s'ha escollit cap fitxer",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "No hi ha cap grup amb accés, afegeix un grup per concedir accés",
|
"No groups with access, add a group to grant access": "No hi ha cap grup amb accés, afegeix un grup per concedir accés",
|
||||||
"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
|
"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
|
||||||
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
|
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "No s'ha seleccionat cap model",
|
"No models selected": "No s'ha seleccionat cap model",
|
||||||
"No Notes": "No hi ha notes",
|
"No Notes": "No hi ha notes",
|
||||||
"No notes found": "No s'han trobat notes",
|
"No notes found": "No s'han trobat notes",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "No s'han trobat resultats",
|
"No results": "No s'han trobat resultats",
|
||||||
"No results found": "No s'han trobat resultats",
|
"No results found": "No s'han trobat resultats",
|
||||||
"No search query generated": "No s'ha generat cap consulta",
|
"No search query generated": "No s'ha generat cap consulta",
|
||||||
"No source available": "Sense font disponible",
|
"No source available": "Sense font disponible",
|
||||||
"No sources found": "No s'han trobat fonts",
|
"No sources found": "No s'han trobat fonts",
|
||||||
"No suggestion prompts": "Cap prompt suggerit",
|
"No suggestion prompts": "Cap prompt suggerit",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "No s'han trobat usuaris",
|
"No users were found.": "No s'han trobat usuaris",
|
||||||
"No valves": "No hi ha valves",
|
"No valves": "No hi ha valves",
|
||||||
"No valves to update": "No hi ha cap Valve per actualitzar",
|
"No valves to update": "No hi ha cap Valve per actualitzar",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "Públic",
|
"Public": "Públic",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
|
||||||
"Pull a model from Ollama.com": "Obtenir un model d'Ollama.com",
|
"Pull a model from Ollama.com": "Obtenir un model d'Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "pypdfium2",
|
"pypdfium2": "pypdfium2",
|
||||||
"Query Generation Prompt": "Indicació per a generació de consulta",
|
"Query Generation Prompt": "Indicació per a generació de consulta",
|
||||||
"Querying": "Consultes",
|
"Querying": "Consultes",
|
||||||
|
|
@ -1373,6 +1384,7 @@
|
||||||
"Select how to split message text for TTS requests": "Seleccionar com separar un missatge per a peticions TTS",
|
"Select how to split message text for TTS requests": "Seleccionar com separar un missatge per a peticions TTS",
|
||||||
"Select Knowledge": "Seleccionar coneixement",
|
"Select Knowledge": "Seleccionar coneixement",
|
||||||
"Select only one model to call": "Seleccionar només un model per trucar",
|
"Select only one model to call": "Seleccionar només un model per trucar",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "El(s) model(s) seleccionats no admeten l'entrada d'imatges",
|
"Selected model(s) do not support image inputs": "El(s) model(s) seleccionats no admeten l'entrada d'imatges",
|
||||||
"semantic": "semàntic",
|
"semantic": "semàntic",
|
||||||
"Send": "Enviar",
|
"Send": "Enviar",
|
||||||
|
|
@ -1413,6 +1425,7 @@
|
||||||
"Share Chat": "Compartir el xat",
|
"Share Chat": "Compartir el xat",
|
||||||
"Share to Open WebUI Community": "Compartir amb la comunitat OpenWebUI",
|
"Share to Open WebUI Community": "Compartir amb la comunitat OpenWebUI",
|
||||||
"Share your background and interests": "Compartir la teva informació i interessos",
|
"Share your background and interests": "Compartir la teva informació i interessos",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "Compartir els permisos",
|
"Sharing Permissions": "Compartir els permisos",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Les dreceres de teclat amb un asterisc (*) són situacionals i només actives sota condicions específiques.",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Les dreceres de teclat amb un asterisc (*) són situacionals i només actives sota condicions específiques.",
|
||||||
"Show": "Mostrar",
|
"Show": "Mostrar",
|
||||||
|
|
@ -1480,6 +1493,7 @@
|
||||||
"System Instructions": "Instruccions de sistema",
|
"System Instructions": "Instruccions de sistema",
|
||||||
"System Prompt": "Indicació del Sistema",
|
"System Prompt": "Indicació del Sistema",
|
||||||
"Table Mode": "Mode de taula",
|
"Table Mode": "Mode de taula",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "Etiquetes",
|
"Tags": "Etiquetes",
|
||||||
"Tags Generation": "Generació d'etiquetes",
|
"Tags Generation": "Generació d'etiquetes",
|
||||||
"Tags Generation Prompt": "Indicació per a la generació d'etiquetes",
|
"Tags Generation Prompt": "Indicació per a la generació d'etiquetes",
|
||||||
|
|
@ -1591,6 +1605,7 @@
|
||||||
"Transformers": "Transformadors",
|
"Transformers": "Transformadors",
|
||||||
"Trouble accessing Ollama?": "Problemes en accedir a Ollama?",
|
"Trouble accessing Ollama?": "Problemes en accedir a Ollama?",
|
||||||
"Trust Proxy Environment": "Confiar en l'entorn proxy",
|
"Trust Proxy Environment": "Confiar en l'entorn proxy",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "Tornar a intentar-ho",
|
"Try Again": "Tornar a intentar-ho",
|
||||||
"TTS Model": "Model TTS",
|
"TTS Model": "Model TTS",
|
||||||
"TTS Settings": "Preferències de TTS",
|
"TTS Settings": "Preferències de TTS",
|
||||||
|
|
@ -1627,6 +1642,7 @@
|
||||||
"Upload directory": "Pujar directori",
|
"Upload directory": "Pujar directori",
|
||||||
"Upload files": "Pujar fitxers",
|
"Upload files": "Pujar fitxers",
|
||||||
"Upload Files": "Pujar fitxers",
|
"Upload Files": "Pujar fitxers",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "Pujar una Pipeline",
|
"Upload Pipeline": "Pujar una Pipeline",
|
||||||
"Upload Progress": "Progrés de càrrega",
|
"Upload Progress": "Progrés de càrrega",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "Progrés de la pujada: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "Progrés de la pujada: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "",
|
"Add User Group": "",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "Ang pag-adjust niini nga mga setting magamit ang mga pagbag-o sa tanan nga tiggamit.",
|
"Adjusting these settings will apply changes universally to all users.": "Ang pag-adjust niini nga mga setting magamit ang mga pagbag-o sa tanan nga tiggamit.",
|
||||||
"admin": "Administrator",
|
"admin": "Administrator",
|
||||||
"Admin": "",
|
"Admin": "",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "",
|
"Create Folder": "",
|
||||||
"Create Group": "",
|
"Create Group": "",
|
||||||
"Create Knowledge": "",
|
"Create Knowledge": "",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "",
|
"Create new key": "",
|
||||||
"Create new secret key": "",
|
"Create new secret key": "",
|
||||||
"Create Note": "",
|
"Create Note": "",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Gihimo ang",
|
"Created at": "Gihimo ang",
|
||||||
"Created At": "",
|
"Created At": "",
|
||||||
"Created by": "",
|
"Created by": "",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "",
|
"CSV Import": "",
|
||||||
"Ctrl+Enter to Send": "",
|
"Ctrl+Enter to Send": "",
|
||||||
"Current Model": "Kasamtangang modelo",
|
"Current Model": "Kasamtangang modelo",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "",
|
"Delete function?": "",
|
||||||
"Delete Message": "",
|
"Delete Message": "",
|
||||||
"Delete message?": "",
|
"Delete message?": "",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "",
|
"Delete note?": "",
|
||||||
"Delete prompt?": "",
|
"Delete prompt?": "",
|
||||||
"delete this link": "",
|
"delete this link": "",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "",
|
"Enter a detail about yourself for your LLMs to recall": "",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "",
|
"Enter a watermark for the response. Leave empty for none.": "",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "",
|
"Enter api auth string (e.g. username:password)": "",
|
||||||
"Enter Application DN": "",
|
"Enter Application DN": "",
|
||||||
"Enter Application DN Password": "",
|
"Enter Application DN Password": "",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "",
|
"Export chat (.json)": "",
|
||||||
"Export Chats": "I-export ang mga chat",
|
"Export Chats": "I-export ang mga chat",
|
||||||
"Export Config to JSON File": "",
|
"Export Config to JSON File": "",
|
||||||
"Export Functions": "",
|
|
||||||
"Export Models": "",
|
|
||||||
"Export Presets": "",
|
"Export Presets": "",
|
||||||
"Export Prompt Suggestions": "",
|
"Export Prompt Suggestions": "",
|
||||||
"Export Prompts": "Export prompts",
|
|
||||||
"Export to CSV": "",
|
"Export to CSV": "",
|
||||||
"Export Tools": "",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "",
|
"External": "",
|
||||||
"External Document Loader URL required.": "",
|
"External Document Loader URL required.": "",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "",
|
"Failed to load file content.": "",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
|
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "",
|
"Failed to save connections": "",
|
||||||
"Failed to save conversation": "Napakyas sa pagtipig sa panag-istorya",
|
"Failed to save conversation": "Napakyas sa pagtipig sa panag-istorya",
|
||||||
"Failed to save models configuration": "",
|
"Failed to save models configuration": "",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "",
|
"Firecrawl API Key": "",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "Pag-focus sa entry sa diskusyon",
|
"Focus chat input": "Pag-focus sa entry sa diskusyon",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "",
|
"Folder deleted successfully": "",
|
||||||
"Folder Name": "",
|
"Folder Name": "",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "",
|
"H2": "",
|
||||||
"H3": "",
|
"H3": "",
|
||||||
"Haptic Feedback": "",
|
"Haptic Feedback": "",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "Maayong buntag, {{name}}",
|
"Hello, {{name}}": "Maayong buntag, {{name}}",
|
||||||
"Help": "",
|
"Help": "",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Import nga mga chat",
|
"Import Chats": "Import nga mga chat",
|
||||||
"Import Config from JSON File": "",
|
"Import Config from JSON File": "",
|
||||||
"Import From Link": "",
|
"Import From Link": "",
|
||||||
"Import Functions": "",
|
|
||||||
"Import Models": "",
|
|
||||||
"Import Notes": "",
|
"Import Notes": "",
|
||||||
"Import Presets": "",
|
"Import Presets": "",
|
||||||
"Import Prompt Suggestions": "",
|
"Import Prompt Suggestions": "",
|
||||||
"Import Prompts": "Import prompt",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "",
|
|
||||||
"Important Update": "Mahinungdanong update",
|
"Important Update": "Mahinungdanong update",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "",
|
"Include": "",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "",
|
"Invalid file format.": "",
|
||||||
"Invalid JSON file": "",
|
"Invalid JSON file": "",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "",
|
"Invalid Tag": "",
|
||||||
"is typing...": "",
|
"is typing...": "",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Bag-ong diskusyon",
|
"New Chat": "Bag-ong diskusyon",
|
||||||
"New Folder": "",
|
"New Folder": "",
|
||||||
"New Function": "",
|
"New Function": "",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "",
|
"New Note": "",
|
||||||
"New Password": "Bag-ong Password",
|
"New Password": "Bag-ong Password",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "",
|
"New Tool": "",
|
||||||
"new-channel": "",
|
"new-channel": "",
|
||||||
"Next message": "",
|
"Next message": "",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "",
|
"No distance available": "",
|
||||||
"No feedbacks found": "",
|
"No feedbacks found": "",
|
||||||
"No file selected": "",
|
"No file selected": "",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "",
|
"No groups with access, add a group to grant access": "",
|
||||||
"No HTML, CSS, or JavaScript content found.": "",
|
"No HTML, CSS, or JavaScript content found.": "",
|
||||||
"No inference engine with management support found": "",
|
"No inference engine with management support found": "",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "",
|
"No models selected": "",
|
||||||
"No Notes": "",
|
"No Notes": "",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "Walay resulta",
|
"No results": "Walay resulta",
|
||||||
"No results found": "",
|
"No results found": "",
|
||||||
"No search query generated": "",
|
"No search query generated": "",
|
||||||
"No source available": "Walay tinubdan nga anaa",
|
"No source available": "Walay tinubdan nga anaa",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "Walay gisugyot nga prompt",
|
"No suggestion prompts": "Walay gisugyot nga prompt",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "",
|
"No users were found.": "",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "",
|
"No valves to update": "",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "",
|
"Public": "",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "",
|
"Pull \"{{searchValue}}\" from Ollama.com": "",
|
||||||
"Pull a model from Ollama.com": "Pagkuha ug template gikan sa Ollama.com",
|
"Pull a model from Ollama.com": "Pagkuha ug template gikan sa Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "",
|
"Query Generation Prompt": "",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1372,6 +1383,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "",
|
"Select Knowledge": "",
|
||||||
"Select only one model to call": "",
|
"Select only one model to call": "",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "",
|
"Selected model(s) do not support image inputs": "",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "",
|
"Send": "",
|
||||||
|
|
@ -1412,6 +1424,7 @@
|
||||||
"Share Chat": "",
|
"Share Chat": "",
|
||||||
"Share to Open WebUI Community": "Ipakigbahin sa komunidad sa OpenWebUI",
|
"Share to Open WebUI Community": "Ipakigbahin sa komunidad sa OpenWebUI",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "",
|
"Sharing Permissions": "",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
|
||||||
"Show": "Pagpakita",
|
"Show": "Pagpakita",
|
||||||
|
|
@ -1479,6 +1492,7 @@
|
||||||
"System Instructions": "",
|
"System Instructions": "",
|
||||||
"System Prompt": "Madasig nga Sistema",
|
"System Prompt": "Madasig nga Sistema",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "",
|
"Tags": "",
|
||||||
"Tags Generation": "",
|
"Tags Generation": "",
|
||||||
"Tags Generation Prompt": "",
|
"Tags Generation Prompt": "",
|
||||||
|
|
@ -1590,6 +1604,7 @@
|
||||||
"Transformers": "",
|
"Transformers": "",
|
||||||
"Trouble accessing Ollama?": "Adunay mga problema sa pag-access sa Ollama?",
|
"Trouble accessing Ollama?": "Adunay mga problema sa pag-access sa Ollama?",
|
||||||
"Trust Proxy Environment": "",
|
"Trust Proxy Environment": "",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "",
|
"TTS Model": "",
|
||||||
"TTS Settings": "Mga Setting sa TTS",
|
"TTS Settings": "Mga Setting sa TTS",
|
||||||
|
|
@ -1626,6 +1641,7 @@
|
||||||
"Upload directory": "",
|
"Upload directory": "",
|
||||||
"Upload files": "",
|
"Upload files": "",
|
||||||
"Upload Files": "",
|
"Upload Files": "",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "",
|
"Upload Pipeline": "",
|
||||||
"Upload Progress": "Pag-uswag sa Pag-upload",
|
"Upload Progress": "Pag-uswag sa Pag-upload",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "Přidat skupinu uživatelů",
|
"Add User Group": "Přidat skupinu uživatelů",
|
||||||
"Additional Config": "Dodatečná konfigurace",
|
"Additional Config": "Dodatečná konfigurace",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "Další možnosti konfigurace pro marker. Měl by to být řetězec JSON s páry klíč-hodnota. Například: '{\"key\": \"value\"}'. Podporované klíče zahrnují: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "Další možnosti konfigurace pro marker. Měl by to být řetězec JSON s páry klíč-hodnota. Například: '{\"key\": \"value\"}'. Podporované klíče zahrnují: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "Úprava těchto nastavení se projeví u všech uživatelů.",
|
"Adjusting these settings will apply changes universally to all users.": "Úprava těchto nastavení se projeví u všech uživatelů.",
|
||||||
"admin": "administrátor",
|
"admin": "administrátor",
|
||||||
"Admin": "Administrátor",
|
"Admin": "Administrátor",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "Vytvořit složku",
|
"Create Folder": "Vytvořit složku",
|
||||||
"Create Group": "Vytvořit skupinu",
|
"Create Group": "Vytvořit skupinu",
|
||||||
"Create Knowledge": "Vytvořit znalost",
|
"Create Knowledge": "Vytvořit znalost",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "Vytvořit nový klíč",
|
"Create new key": "Vytvořit nový klíč",
|
||||||
"Create new secret key": "Vytvořit nový tajný klíč",
|
"Create new secret key": "Vytvořit nový tajný klíč",
|
||||||
"Create Note": "Vytvořit poznámku",
|
"Create Note": "Vytvořit poznámku",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Vytvořeno",
|
"Created at": "Vytvořeno",
|
||||||
"Created At": "Vytvořeno",
|
"Created At": "Vytvořeno",
|
||||||
"Created by": "Vytvořil/a",
|
"Created by": "Vytvořil/a",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "Import z CSV",
|
"CSV Import": "Import z CSV",
|
||||||
"Ctrl+Enter to Send": "Ctrl+Enter pro odeslání",
|
"Ctrl+Enter to Send": "Ctrl+Enter pro odeslání",
|
||||||
"Current Model": "Aktuální model",
|
"Current Model": "Aktuální model",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "Smazat funkci?",
|
"Delete function?": "Smazat funkci?",
|
||||||
"Delete Message": "Smazat zprávu",
|
"Delete Message": "Smazat zprávu",
|
||||||
"Delete message?": "Smazat zprávu?",
|
"Delete message?": "Smazat zprávu?",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "Smazat poznámku?",
|
"Delete note?": "Smazat poznámku?",
|
||||||
"Delete prompt?": "Smazat instrukci?",
|
"Delete prompt?": "Smazat instrukci?",
|
||||||
"delete this link": "smazat tento odkaz",
|
"delete this link": "smazat tento odkaz",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "Zadejte podrobnost o sobě, kterou si vaše LLM mají pamatovat.",
|
"Enter a detail about yourself for your LLMs to recall": "Zadejte podrobnost o sobě, kterou si vaše LLM mají pamatovat.",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "Zadejte název pro překryvnou vrstvu s informacemi o čekajícím uživateli. Pro výchozí ponechte prázdné.",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "Zadejte název pro překryvnou vrstvu s informacemi o čekajícím uživateli. Pro výchozí ponechte prázdné.",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "Zadejte vodoznak pro odpověď. Pro žádný vodoznak ponechte prázdné.",
|
"Enter a watermark for the response. Leave empty for none.": "Zadejte vodoznak pro odpověď. Pro žádný vodoznak ponechte prázdné.",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "Zadejte ověřovací řetězec API (např. uzivatelske_jmeno:heslo)",
|
"Enter api auth string (e.g. username:password)": "Zadejte ověřovací řetězec API (např. uzivatelske_jmeno:heslo)",
|
||||||
"Enter Application DN": "Zadejte Application DN",
|
"Enter Application DN": "Zadejte Application DN",
|
||||||
"Enter Application DN Password": "Zadejte heslo pro Application DN",
|
"Enter Application DN Password": "Zadejte heslo pro Application DN",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "Exportovat konverzaci (.json)",
|
"Export chat (.json)": "Exportovat konverzaci (.json)",
|
||||||
"Export Chats": "Exportovat konverzace",
|
"Export Chats": "Exportovat konverzace",
|
||||||
"Export Config to JSON File": "Exportovat konfiguraci do souboru JSON",
|
"Export Config to JSON File": "Exportovat konfiguraci do souboru JSON",
|
||||||
"Export Functions": "Exportovat funkce",
|
|
||||||
"Export Models": "Exportovat modely",
|
|
||||||
"Export Presets": "Exportovat předvolby",
|
"Export Presets": "Exportovat předvolby",
|
||||||
"Export Prompt Suggestions": "Exportovat návrhy instrukcí",
|
"Export Prompt Suggestions": "Exportovat návrhy instrukcí",
|
||||||
"Export Prompts": "Exportovat instrukce",
|
|
||||||
"Export to CSV": "Exportovat do CSV",
|
"Export to CSV": "Exportovat do CSV",
|
||||||
"Export Tools": "Exportovat nástroje",
|
|
||||||
"Export Users": "Exportovat uživatele",
|
"Export Users": "Exportovat uživatele",
|
||||||
"External": "Externí",
|
"External": "Externí",
|
||||||
"External Document Loader URL required.": "Je vyžadována URL externího zavaděče dokumentů.",
|
"External Document Loader URL required.": "Je vyžadována URL externího zavaděče dokumentů.",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "Nepodařilo se načíst obsah souboru.",
|
"Failed to load file content.": "Nepodařilo se načíst obsah souboru.",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
|
"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "Nepodařilo se uložit připojení",
|
"Failed to save connections": "Nepodařilo se uložit připojení",
|
||||||
"Failed to save conversation": "Nepodařilo se uložit konverzaci",
|
"Failed to save conversation": "Nepodařilo se uložit konverzaci",
|
||||||
"Failed to save models configuration": "Nepodařilo se uložit konfiguraci modelů",
|
"Failed to save models configuration": "Nepodařilo se uložit konfiguraci modelů",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "API klíč pro Firecrawl",
|
"Firecrawl API Key": "API klíč pro Firecrawl",
|
||||||
"Floating Quick Actions": "Plovoucí rychlé akce",
|
"Floating Quick Actions": "Plovoucí rychlé akce",
|
||||||
"Focus chat input": "Zaměřit vstupní pole konverzace",
|
"Focus chat input": "Zaměřit vstupní pole konverzace",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "Složka byla úspěšně smazána",
|
"Folder deleted successfully": "Složka byla úspěšně smazána",
|
||||||
"Folder Name": "Název složky",
|
"Folder Name": "Název složky",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "H2",
|
"H2": "H2",
|
||||||
"H3": "H3",
|
"H3": "H3",
|
||||||
"Haptic Feedback": "Haptická odezva",
|
"Haptic Feedback": "Haptická odezva",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "Výška",
|
"Height": "Výška",
|
||||||
"Hello, {{name}}": "Dobrý den, {{name}}",
|
"Hello, {{name}}": "Dobrý den, {{name}}",
|
||||||
"Help": "Nápověda",
|
"Help": "Nápověda",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Importovat konverzace",
|
"Import Chats": "Importovat konverzace",
|
||||||
"Import Config from JSON File": "Importovat konfiguraci ze souboru JSON",
|
"Import Config from JSON File": "Importovat konfiguraci ze souboru JSON",
|
||||||
"Import From Link": "Importovat z odkazu",
|
"Import From Link": "Importovat z odkazu",
|
||||||
"Import Functions": "Importovat funkce",
|
|
||||||
"Import Models": "Importovat modely",
|
|
||||||
"Import Notes": "Importovat poznámky",
|
"Import Notes": "Importovat poznámky",
|
||||||
"Import Presets": "Importovat předvolby",
|
"Import Presets": "Importovat předvolby",
|
||||||
"Import Prompt Suggestions": "Importovat návrhy instrukcí",
|
"Import Prompt Suggestions": "Importovat návrhy instrukcí",
|
||||||
"Import Prompts": "Importovat instrukce",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "Importovat nástroje",
|
|
||||||
"Important Update": "Důležitá aktualizace",
|
"Important Update": "Důležitá aktualizace",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "Zahrnout",
|
"Include": "Zahrnout",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "Neplatný formát souboru.",
|
"Invalid file format.": "Neplatný formát souboru.",
|
||||||
"Invalid JSON file": "Neplatný soubor JSON",
|
"Invalid JSON file": "Neplatný soubor JSON",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "Neplatný formát JSON pro pracovní postup ComfyUI.",
|
"Invalid JSON format for ComfyUI Workflow.": "Neplatný formát JSON pro pracovní postup ComfyUI.",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "Neplatný formát JSON v dodatečné konfiguraci",
|
"Invalid JSON format in Additional Config": "Neplatný formát JSON v dodatečné konfiguraci",
|
||||||
"Invalid Tag": "Neplatný štítek",
|
"Invalid Tag": "Neplatný štítek",
|
||||||
"is typing...": "píše...",
|
"is typing...": "píše...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Nová konverzace",
|
"New Chat": "Nová konverzace",
|
||||||
"New Folder": "Nová složka",
|
"New Folder": "Nová složka",
|
||||||
"New Function": "Nová funkce",
|
"New Function": "Nová funkce",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "Nová poznámka",
|
"New Note": "Nová poznámka",
|
||||||
"New Password": "Nové heslo",
|
"New Password": "Nové heslo",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "Nový nástroj",
|
"New Tool": "Nový nástroj",
|
||||||
"new-channel": "novy-kanal",
|
"new-channel": "novy-kanal",
|
||||||
"Next message": "Další zpráva",
|
"Next message": "Další zpráva",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "Vzdálenost není k dispozici",
|
"No distance available": "Vzdálenost není k dispozici",
|
||||||
"No feedbacks found": "Nebyla nalezena žádná zpětná vazba",
|
"No feedbacks found": "Nebyla nalezena žádná zpětná vazba",
|
||||||
"No file selected": "Nebyl vybrán žádný soubor",
|
"No file selected": "Nebyl vybrán žádný soubor",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "Žádné skupiny s přístupem, přidejte skupinu pro udělení přístupu",
|
"No groups with access, add a group to grant access": "Žádné skupiny s přístupem, přidejte skupinu pro udělení přístupu",
|
||||||
"No HTML, CSS, or JavaScript content found.": "Nebyl nalezen žádný obsah HTML, CSS ani JavaScriptu.",
|
"No HTML, CSS, or JavaScript content found.": "Nebyl nalezen žádný obsah HTML, CSS ani JavaScriptu.",
|
||||||
"No inference engine with management support found": "Nebyl nalezeno žádné inferenční jádro s podporou správy",
|
"No inference engine with management support found": "Nebyl nalezeno žádné inferenční jádro s podporou správy",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "Nebyly vybrány žádné modely",
|
"No models selected": "Nebyly vybrány žádné modely",
|
||||||
"No Notes": "Žádné poznámky",
|
"No Notes": "Žádné poznámky",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "Nebyly nalezeny žádné výsledky",
|
"No results": "Nebyly nalezeny žádné výsledky",
|
||||||
"No results found": "Nebyly nalezeny žádné výsledky",
|
"No results found": "Nebyly nalezeny žádné výsledky",
|
||||||
"No search query generated": "Nebyl vygenerován žádný vyhledávací dotaz.",
|
"No search query generated": "Nebyl vygenerován žádný vyhledávací dotaz.",
|
||||||
"No source available": "Není k dispozici žádný zdroj.",
|
"No source available": "Není k dispozici žádný zdroj.",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "Žádné návrhy promptů",
|
"No suggestion prompts": "Žádné návrhy promptů",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "Nebyli nalezeni žádní uživatelé.",
|
"No users were found.": "Nebyli nalezeni žádní uživatelé.",
|
||||||
"No valves": "Žádné ventily",
|
"No valves": "Žádné ventily",
|
||||||
"No valves to update": "Žádné ventily k aktualizaci",
|
"No valves to update": "Žádné ventily k aktualizaci",
|
||||||
|
|
@ -1225,6 +1235,7 @@
|
||||||
"Public": "Veřejné",
|
"Public": "Veřejné",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Stáhnout \"{{searchValue}}\" z Ollama.com",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Stáhnout \"{{searchValue}}\" z Ollama.com",
|
||||||
"Pull a model from Ollama.com": "Stáhnout model z Ollama.com",
|
"Pull a model from Ollama.com": "Stáhnout model z Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "Instrukce pro generování dotazu",
|
"Query Generation Prompt": "Instrukce pro generování dotazu",
|
||||||
"Querying": "",
|
"Querying": "",
|
||||||
|
|
@ -1374,6 +1385,7 @@
|
||||||
"Select how to split message text for TTS requests": "Vyberte, jak dělit text zprávy pro požadavky TTS",
|
"Select how to split message text for TTS requests": "Vyberte, jak dělit text zprávy pro požadavky TTS",
|
||||||
"Select Knowledge": "Vybrat znalosti",
|
"Select Knowledge": "Vybrat znalosti",
|
||||||
"Select only one model to call": "Vyberte pouze jeden model k volání",
|
"Select only one model to call": "Vyberte pouze jeden model k volání",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "Vybraný model (modely) nepodporuje obrazové vstupy.",
|
"Selected model(s) do not support image inputs": "Vybraný model (modely) nepodporuje obrazové vstupy.",
|
||||||
"semantic": "sémantický",
|
"semantic": "sémantický",
|
||||||
"Send": "Odeslat",
|
"Send": "Odeslat",
|
||||||
|
|
@ -1414,6 +1426,7 @@
|
||||||
"Share Chat": "Sdílet konverzaci",
|
"Share Chat": "Sdílet konverzaci",
|
||||||
"Share to Open WebUI Community": "Sdílet s komunitou Open WebUI",
|
"Share to Open WebUI Community": "Sdílet s komunitou Open WebUI",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "Oprávnění pro sdílení",
|
"Sharing Permissions": "Oprávnění pro sdílení",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Zkratky s hvězdičkou (*) jsou situační a aktivní pouze za specifických podmínek.",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Zkratky s hvězdičkou (*) jsou situační a aktivní pouze za specifických podmínek.",
|
||||||
"Show": "Zobrazit",
|
"Show": "Zobrazit",
|
||||||
|
|
@ -1481,6 +1494,7 @@
|
||||||
"System Instructions": "Systémové instrukce",
|
"System Instructions": "Systémové instrukce",
|
||||||
"System Prompt": "Systémové instrukce",
|
"System Prompt": "Systémové instrukce",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "Štítky",
|
"Tags": "Štítky",
|
||||||
"Tags Generation": "Generování štítků",
|
"Tags Generation": "Generování štítků",
|
||||||
"Tags Generation Prompt": "Instrukce pro generování štítků",
|
"Tags Generation Prompt": "Instrukce pro generování štítků",
|
||||||
|
|
@ -1592,6 +1606,7 @@
|
||||||
"Transformers": "Transformers",
|
"Transformers": "Transformers",
|
||||||
"Trouble accessing Ollama?": "Máte potíže s přístupem k Ollama?",
|
"Trouble accessing Ollama?": "Máte potíže s přístupem k Ollama?",
|
||||||
"Trust Proxy Environment": "Důvěřovat prostředí proxy",
|
"Trust Proxy Environment": "Důvěřovat prostředí proxy",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "Zkusit znovu",
|
"Try Again": "Zkusit znovu",
|
||||||
"TTS Model": "Model TTS",
|
"TTS Model": "Model TTS",
|
||||||
"TTS Settings": "Nastavení TTS",
|
"TTS Settings": "Nastavení TTS",
|
||||||
|
|
@ -1628,6 +1643,7 @@
|
||||||
"Upload directory": "Adresář pro nahrávání",
|
"Upload directory": "Adresář pro nahrávání",
|
||||||
"Upload files": "Nahrát soubory",
|
"Upload files": "Nahrát soubory",
|
||||||
"Upload Files": "Nahrát soubory",
|
"Upload Files": "Nahrát soubory",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "Nahrát potrubí",
|
"Upload Pipeline": "Nahrát potrubí",
|
||||||
"Upload Progress": "Průběh nahrávání",
|
"Upload Progress": "Průběh nahrávání",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "Průběh nahrávání: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "Průběh nahrávání: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
"Add User Group": "Tilføj Brugergruppe",
|
"Add User Group": "Tilføj Brugergruppe",
|
||||||
"Additional Config": "",
|
"Additional Config": "",
|
||||||
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
"Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "",
|
||||||
|
"Additional Parameters": "",
|
||||||
"Adjusting these settings will apply changes universally to all users.": "Ændringer af disse indstillinger har konsekvenser for alle brugere.",
|
"Adjusting these settings will apply changes universally to all users.": "Ændringer af disse indstillinger har konsekvenser for alle brugere.",
|
||||||
"admin": "administrator",
|
"admin": "administrator",
|
||||||
"Admin": "Administrator",
|
"Admin": "Administrator",
|
||||||
|
|
@ -343,6 +344,7 @@
|
||||||
"Create Folder": "Opret mappe",
|
"Create Folder": "Opret mappe",
|
||||||
"Create Group": "Opret gruppe",
|
"Create Group": "Opret gruppe",
|
||||||
"Create Knowledge": "Opret Viden",
|
"Create Knowledge": "Opret Viden",
|
||||||
|
"Create Model": "",
|
||||||
"Create new key": "Opret en ny nøgle",
|
"Create new key": "Opret en ny nøgle",
|
||||||
"Create new secret key": "Opret en ny hemmelig nøgle",
|
"Create new secret key": "Opret en ny hemmelig nøgle",
|
||||||
"Create Note": "Opret note",
|
"Create Note": "Opret note",
|
||||||
|
|
@ -350,6 +352,7 @@
|
||||||
"Created at": "Oprettet",
|
"Created at": "Oprettet",
|
||||||
"Created At": "Oprettet",
|
"Created At": "Oprettet",
|
||||||
"Created by": "Oprettet af",
|
"Created by": "Oprettet af",
|
||||||
|
"Created by you": "",
|
||||||
"CSV Import": "Importer CSV",
|
"CSV Import": "Importer CSV",
|
||||||
"Ctrl+Enter to Send": "Ctrl+Enter til at sende",
|
"Ctrl+Enter to Send": "Ctrl+Enter til at sende",
|
||||||
"Current Model": "Nuværende model",
|
"Current Model": "Nuværende model",
|
||||||
|
|
@ -396,6 +399,7 @@
|
||||||
"Delete function?": "Slet funktion?",
|
"Delete function?": "Slet funktion?",
|
||||||
"Delete Message": "Slet besked",
|
"Delete Message": "Slet besked",
|
||||||
"Delete message?": "Slet besked?",
|
"Delete message?": "Slet besked?",
|
||||||
|
"Delete Model": "",
|
||||||
"Delete note?": "Slet note?",
|
"Delete note?": "Slet note?",
|
||||||
"Delete prompt?": "Slet prompt?",
|
"Delete prompt?": "Slet prompt?",
|
||||||
"delete this link": "slet dette link",
|
"delete this link": "slet dette link",
|
||||||
|
|
@ -523,6 +527,9 @@
|
||||||
"Enter a detail about yourself for your LLMs to recall": "Indtast en detalje om dig selv, som dine LLMs kan huske",
|
"Enter a detail about yourself for your LLMs to recall": "Indtast en detalje om dig selv, som dine LLMs kan huske",
|
||||||
"Enter a title for the pending user info overlay. Leave empty for default.": "Indtast en titel til afventende bruger informations overlay. Lad være tom for standard.",
|
"Enter a title for the pending user info overlay. Leave empty for default.": "Indtast en titel til afventende bruger informations overlay. Lad være tom for standard.",
|
||||||
"Enter a watermark for the response. Leave empty for none.": "Indtast et vandmærke til svaret. Lad være tom for ingen.",
|
"Enter a watermark for the response. Leave empty for none.": "Indtast et vandmærke til svaret. Lad være tom for ingen.",
|
||||||
|
"Enter additional headers in JSON format": "",
|
||||||
|
"Enter additional headers in JSON format (e.g. {{'{{\"X-Custom-Header\": \"value\"}}'}})": "",
|
||||||
|
"Enter additional parameters in JSON format": "",
|
||||||
"Enter api auth string (e.g. username:password)": "Indtast api-godkendelsesstreng (f.eks. brugernavn:adgangskode)",
|
"Enter api auth string (e.g. username:password)": "Indtast api-godkendelsesstreng (f.eks. brugernavn:adgangskode)",
|
||||||
"Enter Application DN": "Indtast Application DN",
|
"Enter Application DN": "Indtast Application DN",
|
||||||
"Enter Application DN Password": "Indtast Application DN Password",
|
"Enter Application DN Password": "Indtast Application DN Password",
|
||||||
|
|
@ -667,13 +674,9 @@
|
||||||
"Export chat (.json)": "Eksportér chat (.json)",
|
"Export chat (.json)": "Eksportér chat (.json)",
|
||||||
"Export Chats": "Eksportér chats",
|
"Export Chats": "Eksportér chats",
|
||||||
"Export Config to JSON File": "Eksportér konfiguration til JSON-fil",
|
"Export Config to JSON File": "Eksportér konfiguration til JSON-fil",
|
||||||
"Export Functions": "Eksportér funktioner",
|
|
||||||
"Export Models": "Eksportér modeller",
|
|
||||||
"Export Presets": "Eksportér indstillinger",
|
"Export Presets": "Eksportér indstillinger",
|
||||||
"Export Prompt Suggestions": "Eksportér prompt-forslag",
|
"Export Prompt Suggestions": "Eksportér prompt-forslag",
|
||||||
"Export Prompts": "Eksportér prompts",
|
|
||||||
"Export to CSV": "Eksportér til CSV",
|
"Export to CSV": "Eksportér til CSV",
|
||||||
"Export Tools": "Eksportér værktøjer",
|
|
||||||
"Export Users": "",
|
"Export Users": "",
|
||||||
"External": "Ekstern",
|
"External": "Ekstern",
|
||||||
"External Document Loader URL required.": "External Dokument Loader URL påkrævet.",
|
"External Document Loader URL required.": "External Dokument Loader URL påkrævet.",
|
||||||
|
|
@ -698,6 +701,7 @@
|
||||||
"Failed to load file content.": "Kunne ikke indlæse filindhold.",
|
"Failed to load file content.": "Kunne ikke indlæse filindhold.",
|
||||||
"Failed to move chat": "",
|
"Failed to move chat": "",
|
||||||
"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
|
"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
|
||||||
|
"Failed to render diagram": "",
|
||||||
"Failed to save connections": "Kunne ikke gemme forbindelser",
|
"Failed to save connections": "Kunne ikke gemme forbindelser",
|
||||||
"Failed to save conversation": "Kunne ikke gemme samtalen",
|
"Failed to save conversation": "Kunne ikke gemme samtalen",
|
||||||
"Failed to save models configuration": "Kunne ikke gemme modeller konfiguration",
|
"Failed to save models configuration": "Kunne ikke gemme modeller konfiguration",
|
||||||
|
|
@ -731,6 +735,7 @@
|
||||||
"Firecrawl API Key": "Firecrawl API nøgle",
|
"Firecrawl API Key": "Firecrawl API nøgle",
|
||||||
"Floating Quick Actions": "",
|
"Floating Quick Actions": "",
|
||||||
"Focus chat input": "Fokuser på chatinput",
|
"Focus chat input": "Fokuser på chatinput",
|
||||||
|
"Folder": "",
|
||||||
"Folder Background Image": "",
|
"Folder Background Image": "",
|
||||||
"Folder deleted successfully": "Mappe fjernet.",
|
"Folder deleted successfully": "Mappe fjernet.",
|
||||||
"Folder Name": "Mappenavn",
|
"Folder Name": "Mappenavn",
|
||||||
|
|
@ -800,6 +805,8 @@
|
||||||
"H2": "H2",
|
"H2": "H2",
|
||||||
"H3": "H3",
|
"H3": "H3",
|
||||||
"Haptic Feedback": "Haptisk feedback",
|
"Haptic Feedback": "Haptisk feedback",
|
||||||
|
"Headers": "",
|
||||||
|
"Headers must be a valid JSON object": "",
|
||||||
"Height": "",
|
"Height": "",
|
||||||
"Hello, {{name}}": "Hej {{name}}",
|
"Hello, {{name}}": "Hej {{name}}",
|
||||||
"Help": "Hjælp",
|
"Help": "Hjælp",
|
||||||
|
|
@ -841,14 +848,10 @@
|
||||||
"Import Chats": "Importer chats",
|
"Import Chats": "Importer chats",
|
||||||
"Import Config from JSON File": "Importer konfiguration fra JSON-fil",
|
"Import Config from JSON File": "Importer konfiguration fra JSON-fil",
|
||||||
"Import From Link": "Importer fra et link",
|
"Import From Link": "Importer fra et link",
|
||||||
"Import Functions": "Importer funktioner",
|
|
||||||
"Import Models": "Importer modeller",
|
|
||||||
"Import Notes": "Importer noter",
|
"Import Notes": "Importer noter",
|
||||||
"Import Presets": "Importer Presets",
|
"Import Presets": "Importer Presets",
|
||||||
"Import Prompt Suggestions": "Importer prompt forslag",
|
"Import Prompt Suggestions": "Importer prompt forslag",
|
||||||
"Import Prompts": "Importer prompts",
|
|
||||||
"Import successful": "",
|
"Import successful": "",
|
||||||
"Import Tools": "Importer værktøjer",
|
|
||||||
"Important Update": "Vigtig opdatering",
|
"Important Update": "Vigtig opdatering",
|
||||||
"In order to force OCR, performing OCR must be enabled.": "",
|
"In order to force OCR, performing OCR must be enabled.": "",
|
||||||
"Include": "Inkluder",
|
"Include": "Inkluder",
|
||||||
|
|
@ -876,6 +879,7 @@
|
||||||
"Invalid file format.": "Ugyldigt filformat.",
|
"Invalid file format.": "Ugyldigt filformat.",
|
||||||
"Invalid JSON file": "Ugyldig JSON fil",
|
"Invalid JSON file": "Ugyldig JSON fil",
|
||||||
"Invalid JSON format for ComfyUI Workflow.": "",
|
"Invalid JSON format for ComfyUI Workflow.": "",
|
||||||
|
"Invalid JSON format for Parameters": "",
|
||||||
"Invalid JSON format in Additional Config": "",
|
"Invalid JSON format in Additional Config": "",
|
||||||
"Invalid Tag": "Ugyldigt tag",
|
"Invalid Tag": "Ugyldigt tag",
|
||||||
"is typing...": "er i gang med at skrive...",
|
"is typing...": "er i gang med at skrive...",
|
||||||
|
|
@ -1034,8 +1038,11 @@
|
||||||
"New Chat": "Ny chat",
|
"New Chat": "Ny chat",
|
||||||
"New Folder": "Ny mappe",
|
"New Folder": "Ny mappe",
|
||||||
"New Function": "Ny funktion",
|
"New Function": "Ny funktion",
|
||||||
|
"New Knowledge": "",
|
||||||
|
"New Model": "",
|
||||||
"New Note": "Ny note",
|
"New Note": "Ny note",
|
||||||
"New Password": "Ny adgangskode",
|
"New Password": "Ny adgangskode",
|
||||||
|
"New Prompt": "",
|
||||||
"New Tool": "Nyt værktøj",
|
"New Tool": "Nyt værktøj",
|
||||||
"new-channel": "ny-kanal",
|
"new-channel": "ny-kanal",
|
||||||
"Next message": "Næste besked",
|
"Next message": "Næste besked",
|
||||||
|
|
@ -1051,6 +1058,7 @@
|
||||||
"No distance available": "Ingen afstand tilgængelig",
|
"No distance available": "Ingen afstand tilgængelig",
|
||||||
"No feedbacks found": "Ingen feedback fundet",
|
"No feedbacks found": "Ingen feedback fundet",
|
||||||
"No file selected": "Ingen fil valgt",
|
"No file selected": "Ingen fil valgt",
|
||||||
|
"No functions found": "",
|
||||||
"No groups with access, add a group to grant access": "Ingen grupper med adgang, tilføj en gruppe for at give adgang",
|
"No groups with access, add a group to grant access": "Ingen grupper med adgang, tilføj en gruppe for at give adgang",
|
||||||
"No HTML, CSS, or JavaScript content found.": "Intet HTML-, CSS- eller JavaScript-indhold fundet.",
|
"No HTML, CSS, or JavaScript content found.": "Intet HTML-, CSS- eller JavaScript-indhold fundet.",
|
||||||
"No inference engine with management support found": "Ingen inference-engine med støtte til administration fundet",
|
"No inference engine with management support found": "Ingen inference-engine med støtte til administration fundet",
|
||||||
|
|
@ -1061,12 +1069,14 @@
|
||||||
"No models selected": "Ingen modeller valgt",
|
"No models selected": "Ingen modeller valgt",
|
||||||
"No Notes": "Ingen noter",
|
"No Notes": "Ingen noter",
|
||||||
"No notes found": "",
|
"No notes found": "",
|
||||||
|
"No prompts found": "",
|
||||||
"No results": "Ingen resultater fundet",
|
"No results": "Ingen resultater fundet",
|
||||||
"No results found": "Ingen resultater fundet",
|
"No results found": "Ingen resultater fundet",
|
||||||
"No search query generated": "Ingen søgeforespørgsel genereret",
|
"No search query generated": "Ingen søgeforespørgsel genereret",
|
||||||
"No source available": "Ingen kilde tilgængelig",
|
"No source available": "Ingen kilde tilgængelig",
|
||||||
"No sources found": "",
|
"No sources found": "",
|
||||||
"No suggestion prompts": "Ingen forslagsprompter",
|
"No suggestion prompts": "Ingen forslagsprompter",
|
||||||
|
"No tools found": "",
|
||||||
"No users were found.": "Ingen brugere blev fundet.",
|
"No users were found.": "Ingen brugere blev fundet.",
|
||||||
"No valves": "",
|
"No valves": "",
|
||||||
"No valves to update": "Ingen ventiler at opdatere",
|
"No valves to update": "Ingen ventiler at opdatere",
|
||||||
|
|
@ -1225,9 +1235,10 @@
|
||||||
"Public": "Offentlig",
|
"Public": "Offentlig",
|
||||||
"Pull \"{{searchValue}}\" from Ollama.com": "Hent \"{{searchValue}}\" fra Ollama.com",
|
"Pull \"{{searchValue}}\" from Ollama.com": "Hent \"{{searchValue}}\" fra Ollama.com",
|
||||||
"Pull a model from Ollama.com": "Hent en model fra Ollama.com",
|
"Pull a model from Ollama.com": "Hent en model fra Ollama.com",
|
||||||
|
"Pull Model": "",
|
||||||
"pypdfium2": "",
|
"pypdfium2": "",
|
||||||
"Query Generation Prompt": "Forespørgsel genererings prompt",
|
"Query Generation Prompt": "Forespørgsel genererings prompt",
|
||||||
"Querying": "",
|
"Querying": "Undersøger",
|
||||||
"Quick Actions": "",
|
"Quick Actions": "",
|
||||||
"RAG Template": "RAG-skabelon",
|
"RAG Template": "RAG-skabelon",
|
||||||
"Rating": "Rating",
|
"Rating": "Rating",
|
||||||
|
|
@ -1289,10 +1300,10 @@
|
||||||
"RESULT": "Resultat",
|
"RESULT": "Resultat",
|
||||||
"Retrieval": "Hentning",
|
"Retrieval": "Hentning",
|
||||||
"Retrieval Query Generation": "Hentnings forespørgsel generering",
|
"Retrieval Query Generation": "Hentnings forespørgsel generering",
|
||||||
"Retrieved {{count}} sources": "",
|
"Retrieved {{count}} sources": "Fandt en kildehenvisning",
|
||||||
"Retrieved {{count}} sources_one": "",
|
"Retrieved {{count}} sources_one": "",
|
||||||
"Retrieved {{count}} sources_other": "",
|
"Retrieved {{count}} sources_other": "",
|
||||||
"Retrieved 1 source": "",
|
"Retrieved 1 source": "Fandt en kildehenvisning",
|
||||||
"Rich Text Input for Chat": "Rich text input til chat",
|
"Rich Text Input for Chat": "Rich text input til chat",
|
||||||
"RK": "RK",
|
"RK": "RK",
|
||||||
"Role": "Rolle",
|
"Role": "Rolle",
|
||||||
|
|
@ -1372,6 +1383,7 @@
|
||||||
"Select how to split message text for TTS requests": "",
|
"Select how to split message text for TTS requests": "",
|
||||||
"Select Knowledge": "Vælg viden",
|
"Select Knowledge": "Vælg viden",
|
||||||
"Select only one model to call": "Vælg kun én model at kalde",
|
"Select only one model to call": "Vælg kun én model at kalde",
|
||||||
|
"Select view": "",
|
||||||
"Selected model(s) do not support image inputs": "Valgte model(ler) understøtter ikke billedinput",
|
"Selected model(s) do not support image inputs": "Valgte model(ler) understøtter ikke billedinput",
|
||||||
"semantic": "",
|
"semantic": "",
|
||||||
"Send": "Send",
|
"Send": "Send",
|
||||||
|
|
@ -1412,6 +1424,7 @@
|
||||||
"Share Chat": "Del chat",
|
"Share Chat": "Del chat",
|
||||||
"Share to Open WebUI Community": "Del til OpenWebUI Community",
|
"Share to Open WebUI Community": "Del til OpenWebUI Community",
|
||||||
"Share your background and interests": "",
|
"Share your background and interests": "",
|
||||||
|
"Shared with you": "",
|
||||||
"Sharing Permissions": "Delingstilladelser",
|
"Sharing Permissions": "Delingstilladelser",
|
||||||
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Genveje med en stjerne (*) er situationsbestemte og kun aktive under specifikke betingelser.",
|
"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "Genveje med en stjerne (*) er situationsbestemte og kun aktive under specifikke betingelser.",
|
||||||
"Show": "Vis",
|
"Show": "Vis",
|
||||||
|
|
@ -1479,6 +1492,7 @@
|
||||||
"System Instructions": "Systeminstruktioner",
|
"System Instructions": "Systeminstruktioner",
|
||||||
"System Prompt": "Systemprompt",
|
"System Prompt": "Systemprompt",
|
||||||
"Table Mode": "",
|
"Table Mode": "",
|
||||||
|
"Tag": "",
|
||||||
"Tags": "Tags",
|
"Tags": "Tags",
|
||||||
"Tags Generation": "Tag generering",
|
"Tags Generation": "Tag generering",
|
||||||
"Tags Generation Prompt": "Tag genererings prompt",
|
"Tags Generation Prompt": "Tag genererings prompt",
|
||||||
|
|
@ -1564,7 +1578,7 @@
|
||||||
"To select toolkits here, add them to the \"Tools\" workspace first.": "For at vælge værktøjssæt her skal du først tilføje dem til \"Værktøjer\"-arbejdsområdet.",
|
"To select toolkits here, add them to the \"Tools\" workspace first.": "For at vælge værktøjssæt her skal du først tilføje dem til \"Værktøjer\"-arbejdsområdet.",
|
||||||
"Toast notifications for new updates": "Toast-notifikationer for nye opdateringer",
|
"Toast notifications for new updates": "Toast-notifikationer for nye opdateringer",
|
||||||
"Today": "I dag",
|
"Today": "I dag",
|
||||||
"Today at {{LOCALIZED_TIME}}": "",
|
"Today at {{LOCALIZED_TIME}}": "I dag {{LOCALIZED_TIME}}",
|
||||||
"Toggle search": "Skift søgning",
|
"Toggle search": "Skift søgning",
|
||||||
"Toggle settings": "Skift indstillinger",
|
"Toggle settings": "Skift indstillinger",
|
||||||
"Toggle sidebar": "Skift sidebjælke",
|
"Toggle sidebar": "Skift sidebjælke",
|
||||||
|
|
@ -1590,6 +1604,7 @@
|
||||||
"Transformers": "Transformers",
|
"Transformers": "Transformers",
|
||||||
"Trouble accessing Ollama?": "Problemer med at få adgang til Ollama?",
|
"Trouble accessing Ollama?": "Problemer med at få adgang til Ollama?",
|
||||||
"Trust Proxy Environment": "Stol på Proxymiljø",
|
"Trust Proxy Environment": "Stol på Proxymiljø",
|
||||||
|
"Try adjusting your search or filter to find what you are looking for.": "",
|
||||||
"Try Again": "",
|
"Try Again": "",
|
||||||
"TTS Model": "TTS-model",
|
"TTS Model": "TTS-model",
|
||||||
"TTS Settings": "TTS-indstillinger",
|
"TTS Settings": "TTS-indstillinger",
|
||||||
|
|
@ -1626,6 +1641,7 @@
|
||||||
"Upload directory": "Uploadmappe",
|
"Upload directory": "Uploadmappe",
|
||||||
"Upload files": "Upload filer",
|
"Upload files": "Upload filer",
|
||||||
"Upload Files": "Upload filer",
|
"Upload Files": "Upload filer",
|
||||||
|
"Upload Model": "",
|
||||||
"Upload Pipeline": "Upload pipeline",
|
"Upload Pipeline": "Upload pipeline",
|
||||||
"Upload Progress": "Uploadfremdrift",
|
"Upload Progress": "Uploadfremdrift",
|
||||||
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
"Upload Progress: {{uploadedFiles}}/{{totalFiles}} ({{percentage}}%)": "",
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue