Compare commits
237 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab94468ffa | |||
| 13c7d96515 | |||
| bffbf7e238 | |||
| 90cd6f272f | |||
| c36c63f1e8 | |||
| dc3b2f1f1e | |||
| 1ab66e83c6 | |||
| 3b7926f00d | |||
| e27de61318 | |||
| cda6b5ad17 | |||
| 3f188b0b86 | |||
| b33b49789e | |||
| cce1762cb8 | |||
| 1fbbd36433 | |||
| f6b955e6a0 | |||
| 41ca399dfd | |||
| 5385d39420 | |||
| d4a18a5765 | |||
| 3f547eb395 | |||
| 336984c254 | |||
| 58bb4758df | |||
| 102b56b938 | |||
| e41a2682f5 | |||
| f6f8c08cb0 | |||
| 8e831a5433 | |||
| c778345e84 | |||
| 0e3c1ac392 | |||
| 2b0af33274 | |||
| 9624493b55 | |||
| e349e26cd8 | |||
| 314b674f32 | |||
| 7766a08b70 | |||
| c5a9cfacf1 | |||
| 8d520eeecd | |||
| f88a80ac47 | |||
| e7da506add | |||
| 2e61ea7cc9 | |||
| 0a5b421ac1 | |||
| a4ea2e0996 | |||
| 23df351239 | |||
| ec9be0d20d | |||
| 533b62f062 | |||
| fba25c6cec | |||
| 6ee924924e | |||
| 9c0a783991 | |||
| 266899acc0 | |||
| 2b0c5f322f | |||
| 08ce91ad86 | |||
| d062af4129 | |||
| 5cda8a57e7 | |||
| b02a38487c | |||
| 2bb6b49f11 | |||
| d6b88c7ef7 | |||
| 1868cf0a91 | |||
| bbffdeb758 | |||
| ce08ea5f6c | |||
| 12a7f4d43b | |||
| 6ba9ccdd79 | |||
| 0b01fd9d6f | |||
| 17c1c2f0f3 | |||
| b8b82b9156 | |||
| 43a490457d | |||
| f58ae6bd17 | |||
| 49e5d01213 | |||
| b20603e01a | |||
| 3adfa29f7d | |||
| a38ce75eda | |||
| 7e971f9dd1 | |||
| 074a443790 | |||
| 454294a101 | |||
| d1c7a3cff3 | |||
| 1b8dc673e7 | |||
| 71039b00a8 | |||
| 16d62d7869 | |||
| 0ae7d12343 | |||
| 1c518ab688 | |||
| ebde5c47d9 | |||
| 2bf68593af | |||
| b61138e676 | |||
| 2517a2b653 | |||
| 6d797820f5 | |||
| f527f31d2b | |||
| 94c6428ae2 | |||
| 1a1cc1514f | |||
| 8fdd3024f7 | |||
| aa7184ae0d | |||
| 51de2ffcc0 | |||
| e5bb0f014a | |||
| 310a18eef2 | |||
| df07d671ce | |||
| 3db6b4352f | |||
| 31c176a485 | |||
| 943a57474f | |||
| 1635dcb69b | |||
| 121a13d4ed | |||
| e3214d08b5 | |||
| db0ff839dd | |||
| d87a7b2f8b | |||
| b891a398fd | |||
| c85ce07f92 | |||
| 5c0854c6b3 | |||
| 3061d4bd10 | |||
| d37a7a1fe1 | |||
| 70b8199030 | |||
| 9b78dde8a9 | |||
| bc56f442c1 | |||
| 6c81cf9795 | |||
| 74cf5e2a2e | |||
| 70116e7251 | |||
| 95c6d0e78c | |||
| ca4b839e6d | |||
| 58d2fd9ac1 | |||
| 78853a67e4 | |||
| 09cd95bffe | |||
| 8685256002 | |||
| 0d33725d21 | |||
| eb1ede119e | |||
| e4fc2e0e51 | |||
| 74172461c7 | |||
| b94b691993 | |||
| fc1cec10be | |||
| 95c94d8a85 | |||
| 5ce4b1c570 | |||
| af6f0641cb | |||
| 642a093d02 | |||
| 28cf616147 | |||
| ffe1abfda9 | |||
| 6b879abdb9 | |||
| a4b8e3d958 | |||
| 442ad41d2b | |||
| 3502e09d9f | |||
| af10f87075 | |||
| 4e375c892a | |||
| bc5452ea41 | |||
| aa2eb37e10 | |||
| c00d1a2f87 | |||
| 824aad4703 | |||
| f661bfde7d | |||
| 9d52120c62 | |||
| 30e4b32a5b | |||
| 7b31c75271 | |||
| 9b640b0ac2 | |||
| e7de41499a | |||
| 1f2b5fa68f | |||
| 681851ca6b | |||
| 74aec6bdf9 | |||
| e3e7176b0b | |||
| 968787ac3e | |||
| e9f05b6cd7 | |||
| 8fdb37a17f | |||
| 4d376c011a | |||
| 5ebcd44163 | |||
| 124fdcc48c | |||
| 48c2fb0d24 | |||
| 0d4d81e012 | |||
| 14bbcd7046 | |||
| cc99673906 | |||
| d6c82939e6 | |||
| 33fffd6b1d | |||
| cd088b2f71 | |||
| a8f2919e9e | |||
| be665f2a3e | |||
| 8727b91664 | |||
| 248fcf1636 | |||
| 7f61d01fde | |||
| 52010accc9 | |||
| 6658e67113 | |||
| 08ad4ee018 | |||
| 5420c165c6 | |||
| 4abede9a2b | |||
| 40a4443949 | |||
| 84c700b92c | |||
| 5fef92c95e | |||
| c2e742afe1 | |||
| a32782e527 | |||
| 6ca295ec59 | |||
| 938a151f12 | |||
| f502e7db51 | |||
| bd24ed953a | |||
| 9418364062 | |||
| b4be58f6b6 | |||
| c5b5303a55 | |||
| c021aba094 | |||
| 8e0c0b4ae3 | |||
| 857b3f0da8 | |||
| af979ce49a | |||
| a1b5c18ef7 | |||
| 8c7e52e5a1 | |||
| 95f4d99e3b | |||
| ce6e8d1264 | |||
| 45e19fb9bd | |||
| cd560a359c | |||
| ec62104211 | |||
| acdcb32ac5 | |||
| 751a61a364 | |||
| 564c0fed95 | |||
| 9eaf01c323 | |||
| 6eb51ab62e | |||
| 6f3c92f6d5 | |||
| 91221cd4b1 | |||
| 2d562ed317 | |||
| dda945f4ad | |||
| 24ab79f5e9 | |||
| 6e306beaa2 | |||
| b72150c881 | |||
| ceaefd8053 | |||
| 2d84db2621 | |||
| 3527023d88 | |||
| e99453219a | |||
| 45f88b80ad | |||
| ba81b47800 | |||
| 646671c576 | |||
| 3d5e361a21 | |||
| bb45d35a36 | |||
| 6a9978dd12 | |||
| 1dd8e5fafd | |||
| dd6de749d5 | |||
| 8fc5532e2f | |||
| 863f73de20 | |||
| 412923dc91 | |||
| 6f17c29d93 | |||
| 6c8d68b6fc | |||
| 4a2792b4da | |||
| 9dd45ddf7c | |||
| dcc57500dd | |||
| e3ae30e42f | |||
| 14e650077a | |||
| 36b9bcee0f | |||
| 6186dec781 | |||
| 667928efb0 | |||
| f4b5039adf | |||
| 8380d3f5ae | |||
| 419005a578 | |||
| 7593d11b97 | |||
| aaf97b85dc | |||
| ea7d4ec6ea | |||
| 1ad9be9c07 |
@@ -5,6 +5,68 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.9] - 2025-02-05
|
||||
|
||||
### Fixed
|
||||
|
||||
- **💡 "Think" Tag Display Issue**: Resolved a bug where the "Think" tag was not functioning correctly, ensuring proper visualization of the model's reasoning process before delivering responses.
|
||||
|
||||
## [0.5.8] - 2025-02-05
|
||||
|
||||
### Added
|
||||
|
||||
- **🖥️ Code Interpreter**: Models can now execute code in real time to refine their answers dynamically, running securely within a sandboxed browser environment using Pyodide. Perfect for calculations, data analysis, and AI-assisted coding tasks!
|
||||
- **💬 Redesigned Chat Input UI**: Enjoy a sleeker and more intuitive message input with improved feature selection, making it easier than ever to toggle tools, enable search, and interact with AI seamlessly.
|
||||
- **🛠️ Native Tool Calling Support (Experimental)**: Supported models can now call tools natively, reducing query latency and improving contextual responses. More enhancements coming soon!
|
||||
- **🔗 Exa Search Engine Integration**: A new search provider has been added, allowing users to retrieve up-to-date and relevant information without leaving the chat interface.
|
||||
- **🌍 Localized Dates & Times**: Date and time formats now match your system locale, ensuring a more natural, region-specific experience.
|
||||
- **📎 User Headers for External Embedding APIs**: API calls to external embedding services now include user-related headers.
|
||||
- **🌍 "Always On" Web Search Toggle**: A new option under Settings > Interface allows users to enable Web Search by default—transform Open WebUI into your go-to search engine, ensuring AI-powered results with every query.
|
||||
- **🚀 General Performance & Stability**: Significant improvements across the platform for a faster, more reliable experience.
|
||||
- **🖼️ UI/UX Enhancements**: Numerous design refinements improving readability, responsiveness, and accessibility.
|
||||
- **🌍 Improved Translations**: Chinese, Korean, French, Ukrainian and Serbian translations have been updated with refined terminologies for better clarity.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🔄 OAuth Name Field Fallback**: Resolves OAuth login failures by using the email field as a fallback when a name is missing.
|
||||
- **🔑 Google Drive Credentials Restriction**: Ensures only authenticated users can access Google Drive credentials for enhanced security.
|
||||
- **🌐 DuckDuckGo Search Rate Limit Handling**: Fixes issues where users would encounter 202 errors due to rate limits when using DuckDuckGo for web search.
|
||||
- **📁 File Upload Permission Indicator**: Users are now notified when they lack permission to upload files, improving clarity on system restrictions.
|
||||
- **🔧 Max Tokens Issue**: Fixes cases where 'max_tokens' were not applied correctly, ensuring proper model behavior.
|
||||
- **🔍 Validation for RAG Web Search URLs**: Filters out invalid or unsupported URLs when using web-based retrieval augmentation.
|
||||
- **🖋️ Title Generation Bug**: Fixes inconsistencies in title generation, ensuring proper chat organization.
|
||||
|
||||
### Removed
|
||||
|
||||
- **⚡ Deprecated Non-Web Worker Pyodide Execution**: Moves entirely to browser sandboxing for better performance and security.
|
||||
|
||||
## [0.5.7] - 2025-01-23
|
||||
|
||||
### Added
|
||||
|
||||
- **🌍 Enhanced Internationalization (i18n)**: Refined and expanded translations for greater global accessibility and a smoother experience for international users.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🔗 Connection Model ID Resolution**: Resolved an issue preventing model IDs from registering in connections.
|
||||
- **💡 Prefix ID for Ollama Connections**: Fixed a bug where prefix IDs in Ollama connections were non-functional.
|
||||
- **🔧 Ollama Model Enable/Disable Functionality**: Addressed the issue of enable/disable toggles not working for Ollama base models.
|
||||
- **🔒 RBAC Permissions for Tools and Models**: Corrected incorrect Role-Based Access Control (RBAC) permissions for tools and models, ensuring that users now only access features according to their assigned privileges, enhancing security and role clarity.
|
||||
|
||||
## [0.5.6] - 2025-01-22
|
||||
|
||||
### Added
|
||||
|
||||
- **🧠 Effortful Reasoning Control for OpenAI Models**: Introduced the reasoning_effort parameter in chat controls for supported OpenAI models, enabling users to fine-tune how much cognitive effort a model dedicates to its responses, offering greater customization for complex queries and reasoning tasks.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **🔄 Chat Controls Loading UI Bug**: Resolved an issue where collapsible chat controls appeared as "loading," ensuring a smoother and more intuitive user experience for managing chat settings.
|
||||
|
||||
### Changed
|
||||
|
||||
- **🔧 Updated Ollama Model Creation**: Revamped the Ollama model creation method to align with their new JSON payload format, ensuring seamless compatibility and more efficient model setup workflows.
|
||||
|
||||
## [0.5.5] - 2025-01-22
|
||||
|
||||
### Added
|
||||
|
||||
@@ -927,6 +927,12 @@ USER_PERMISSIONS_FEATURES_IMAGE_GENERATION = (
|
||||
== "true"
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_CODE_INTERPRETER = (
|
||||
os.environ.get("USER_PERMISSIONS_FEATURES_CODE_INTERPRETER", "True").lower()
|
||||
== "true"
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_USER_PERMISSIONS = {
|
||||
"workspace": {
|
||||
"models": USER_PERMISSIONS_WORKSPACE_MODELS_ACCESS,
|
||||
@@ -944,6 +950,7 @@ DEFAULT_USER_PERMISSIONS = {
|
||||
"features": {
|
||||
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
|
||||
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
|
||||
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1094,21 +1101,27 @@ TITLE_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
|
||||
os.environ.get("TITLE_GENERATION_PROMPT_TEMPLATE", ""),
|
||||
)
|
||||
|
||||
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """Create a concise, 3-5 word title with an emoji as a title for the chat history, in the given language. Suitable Emojis for the summary can be used to enhance understanding but avoid quotation marks or special formatting. RESPOND ONLY WITH THE TITLE TEXT.
|
||||
|
||||
Examples of titles:
|
||||
📉 Stock Market Trends
|
||||
🍪 Perfect Chocolate Chip Recipe
|
||||
Evolution of Music Streaming
|
||||
Remote Work Productivity Tips
|
||||
Artificial Intelligence in Healthcare
|
||||
🎮 Video Game Development Insights
|
||||
|
||||
DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE = """### Task:
|
||||
Generate a concise, 3-5 word title with an emoji summarizing the chat history.
|
||||
### Guidelines:
|
||||
- The title should clearly represent the main theme or subject of the conversation.
|
||||
- Use emojis that enhance understanding of the topic, but avoid quotation marks or special formatting.
|
||||
- Write the title in the chat's primary language; default to English if multilingual.
|
||||
- Prioritize accuracy over excessive creativity; keep it clear and simple.
|
||||
### Output:
|
||||
JSON format: { "title": "your concise title here" }
|
||||
### Examples:
|
||||
- { "title": "📉 Stock Market Trends" },
|
||||
- { "title": "🍪 Perfect Chocolate Chip Recipe" },
|
||||
- { "title": "Evolution of Music Streaming" },
|
||||
- { "title": "Remote Work Productivity Tips" },
|
||||
- { "title": "Artificial Intelligence in Healthcare" },
|
||||
- { "title": "🎮 Video Game Development Insights" }
|
||||
### Chat History:
|
||||
<chat_history>
|
||||
{{MESSAGES:END:2}}
|
||||
</chat_history>"""
|
||||
|
||||
|
||||
TAGS_GENERATION_PROMPT_TEMPLATE = PersistentConfig(
|
||||
"TAGS_GENERATION_PROMPT_TEMPLATE",
|
||||
"task.tags.prompt_template",
|
||||
@@ -1277,7 +1290,28 @@ TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}\nReturn an empty string if no tools match the query. If a function tool matches, construct and return a JSON object in the format {\"name\": \"functionName\", \"parameters\": {\"requiredFunctionParamKey\": \"requiredFunctionParamValue\"}} using the appropriate tool and its parameters. Only return the object and limit the response to the JSON object without additional text."""
|
||||
DEFAULT_TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = """Available Tools: {{TOOLS}}
|
||||
|
||||
Your task is to choose and return the correct tool(s) from the list of available tools based on the query. Follow these guidelines:
|
||||
|
||||
- Return only the JSON object, without any additional text or explanation.
|
||||
|
||||
- If no tools match the query, return an empty array:
|
||||
{
|
||||
"tool_calls": []
|
||||
}
|
||||
|
||||
- If one or more tools match the query, construct a JSON response containing a "tool_calls" array with objects that include:
|
||||
- "name": The tool's name.
|
||||
- "parameters": A dictionary of required parameters and their corresponding values.
|
||||
|
||||
The format for the JSON response is strictly:
|
||||
{
|
||||
"tool_calls": [
|
||||
{"name": "toolName1", "parameters": {"key1": "value1"}},
|
||||
{"name": "toolName2", "parameters": {"key2": "value2"}}
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
DEFAULT_EMOJI_GENERATION_PROMPT_TEMPLATE = """Your task is to reflect the speaker's likely facial expression through a fitting emoji. Interpret emotions from the message and reflect their facial expression using fitting, diverse emojis (e.g., 😊, 😢, 😡, 😱).
|
||||
@@ -1290,6 +1324,24 @@ Your task is to synthesize these responses into a single, high-quality response.
|
||||
|
||||
Responses from models: {{responses}}"""
|
||||
|
||||
|
||||
DEFAULT_CODE_INTERPRETER_PROMPT = """
|
||||
#### Tools Available
|
||||
|
||||
1. **Code Interpreter**: `<code_interpreter type="code" lang="python"></code_interpreter>`
|
||||
- You have access to a Python shell that runs directly in the user's browser, enabling fast execution of code for analysis, calculations, or problem-solving. Use it in this response.
|
||||
- The Python code you write can incorporate a wide array of libraries, handle data manipulation or visualization, perform API calls for web-related tasks, or tackle virtually any computational challenge. Use this flexibility to **think outside the box, craft elegant solutions, and harness Python's full potential**.
|
||||
- To use it, **you must enclose your code within `<code_interpreter type="code" lang="python">` XML tags** and stop right away. If you don't, the code won't execute. Do NOT use triple backticks.
|
||||
- When coding, **always aim to print meaningful outputs** (e.g., results, tables, summaries, or visuals) to better interpret and verify the findings. Avoid relying on implicit outputs; prioritize explicit and clear print statements so the results are effectively communicated to the user.
|
||||
- After obtaining the printed output, **always provide a concise analysis, interpretation, or next steps to help the user understand the findings or refine the outcome further.**
|
||||
- If the results are unclear, unexpected, or require validation, refine the code and execute it again as needed. Always aim to deliver meaningful insights from the results, iterating if necessary.
|
||||
- If a link is provided for an image, audio, or any file, include it in the response exactly as given to ensure the user has access to the original resource.
|
||||
- All responses should be communicated in the chat's primary language, ensuring seamless understanding. If the chat is multilingual, default to English for clarity.
|
||||
- **If a link to an image, audio, or any file is provided in markdown format, explicitly display it as part of the response to ensure the user can access it easily, do NOT change the link.**
|
||||
|
||||
Ensure that the tools are effectively utilized to achieve the highest-quality analysis for the user."""
|
||||
|
||||
|
||||
####################################
|
||||
# Vector Database
|
||||
####################################
|
||||
@@ -1319,6 +1371,7 @@ CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
|
||||
|
||||
MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
|
||||
MILVUS_DB = os.environ.get("MILVUS_DB", "default")
|
||||
MILVUS_TOKEN = os.environ.get("MILVUS_TOKEN", None)
|
||||
|
||||
# Qdrant
|
||||
QDRANT_URI = os.environ.get("QDRANT_URI", None)
|
||||
@@ -1699,6 +1752,11 @@ BING_SEARCH_V7_SUBSCRIPTION_KEY = PersistentConfig(
|
||||
os.environ.get("BING_SEARCH_V7_SUBSCRIPTION_KEY", ""),
|
||||
)
|
||||
|
||||
EXA_API_KEY = PersistentConfig(
|
||||
"EXA_API_KEY",
|
||||
"rag.web.search.exa_api_key",
|
||||
os.getenv("EXA_API_KEY", ""),
|
||||
)
|
||||
|
||||
RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
|
||||
"RAG_WEB_SEARCH_RESULT_COUNT",
|
||||
|
||||
@@ -57,7 +57,7 @@ class ERROR_MESSAGES(str, Enum):
|
||||
)
|
||||
|
||||
FILE_NOT_SENT = "FILE_NOT_SENT"
|
||||
FILE_NOT_SUPPORTED = "Oops! It seems like the file format you're trying to upload is not supported. Please upload a file with a supported format (e.g., JPG, PNG, PDF, TXT) and try again."
|
||||
FILE_NOT_SUPPORTED = "Oops! It seems like the file format you're trying to upload is not supported. Please upload a file with a supported format and try again."
|
||||
|
||||
NOT_FOUND = "We could not find what you're looking for :/"
|
||||
USER_NOT_FOUND = "We could not find what you're looking for :/"
|
||||
|
||||
@@ -356,14 +356,22 @@ WEBUI_SECRET_KEY = os.environ.get(
|
||||
), # DEPRECATED: remove at next major version
|
||||
)
|
||||
|
||||
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
|
||||
"WEBUI_SESSION_COOKIE_SAME_SITE",
|
||||
os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
|
||||
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
|
||||
|
||||
WEBUI_SESSION_COOKIE_SECURE = (
|
||||
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
|
||||
)
|
||||
|
||||
WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
|
||||
"WEBUI_SESSION_COOKIE_SECURE",
|
||||
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
|
||||
WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get(
|
||||
"WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE
|
||||
)
|
||||
|
||||
WEBUI_AUTH_COOKIE_SECURE = (
|
||||
os.environ.get(
|
||||
"WEBUI_AUTH_COOKIE_SECURE",
|
||||
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false"),
|
||||
).lower()
|
||||
== "true"
|
||||
)
|
||||
|
||||
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
|
||||
|
||||
@@ -177,6 +177,7 @@ from open_webui.config import (
|
||||
BING_SEARCH_V7_ENDPOINT,
|
||||
BING_SEARCH_V7_SUBSCRIPTION_KEY,
|
||||
BRAVE_SEARCH_API_KEY,
|
||||
EXA_API_KEY,
|
||||
KAGI_SEARCH_API_KEY,
|
||||
MOJEEK_SEARCH_API_KEY,
|
||||
GOOGLE_PSE_API_KEY,
|
||||
@@ -523,6 +524,7 @@ app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
|
||||
app.state.config.JINA_API_KEY = JINA_API_KEY
|
||||
app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
|
||||
app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
|
||||
app.state.config.EXA_API_KEY = EXA_API_KEY
|
||||
|
||||
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
|
||||
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
|
||||
@@ -859,6 +861,7 @@ async def chat_completion(
|
||||
if model_id not in request.app.state.MODELS:
|
||||
raise Exception("Model not found")
|
||||
model = request.app.state.MODELS[model_id]
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
|
||||
# Check if user has access to the model
|
||||
if not BYPASS_MODEL_ACCESS_CONTROL and user.role == "user":
|
||||
@@ -875,12 +878,25 @@ async def chat_completion(
|
||||
"tool_ids": form_data.get("tool_ids", None),
|
||||
"files": form_data.get("files", None),
|
||||
"features": form_data.get("features", None),
|
||||
"variables": form_data.get("variables", None),
|
||||
"model": model_info,
|
||||
**(
|
||||
{"function_calling": "native"}
|
||||
if form_data.get("params", {}).get("function_calling") == "native"
|
||||
or (
|
||||
model_info
|
||||
and model_info.params.model_dump().get("function_calling")
|
||||
== "native"
|
||||
)
|
||||
else {}
|
||||
),
|
||||
}
|
||||
form_data["metadata"] = metadata
|
||||
|
||||
form_data, events = await process_chat_payload(
|
||||
form_data, metadata, events = await process_chat_payload(
|
||||
request, form_data, metadata, user, model
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -889,6 +905,7 @@ async def chat_completion(
|
||||
|
||||
try:
|
||||
response = await chat_completion_handler(request, form_data, user)
|
||||
|
||||
return await process_chat_response(
|
||||
request, response, form_data, user, events, metadata, tasks
|
||||
)
|
||||
@@ -1007,10 +1024,6 @@ async def get_app_config(request: Request):
|
||||
else {}
|
||||
),
|
||||
},
|
||||
"google_drive": {
|
||||
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
|
||||
"api_key": GOOGLE_DRIVE_API_KEY.value,
|
||||
},
|
||||
**(
|
||||
{
|
||||
"default_models": app.state.config.DEFAULT_MODELS,
|
||||
@@ -1030,6 +1043,10 @@ async def get_app_config(request: Request):
|
||||
"max_count": app.state.config.FILE_MAX_COUNT,
|
||||
},
|
||||
"permissions": {**app.state.config.USER_PERMISSIONS},
|
||||
"google_drive": {
|
||||
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
|
||||
"api_key": GOOGLE_DRIVE_API_KEY.value,
|
||||
},
|
||||
}
|
||||
if user is not None
|
||||
else {}
|
||||
@@ -1063,7 +1080,7 @@ async def get_app_version():
|
||||
|
||||
|
||||
@app.get("/api/version/updates")
|
||||
async def get_app_latest_release_version():
|
||||
async def get_app_latest_release_version(user=Depends(get_verified_user)):
|
||||
if OFFLINE_MODE:
|
||||
log.debug(
|
||||
f"Offline mode is enabled, returning current version as latest version"
|
||||
|
||||
@@ -15,8 +15,13 @@ from langchain_core.documents import Document
|
||||
from open_webui.config import VECTOR_DB
|
||||
from open_webui.retrieval.vector.connector import VECTOR_DB_CLIENT
|
||||
from open_webui.utils.misc import get_last_user_message
|
||||
from open_webui.models.users import UserModel
|
||||
|
||||
from open_webui.env import SRC_LOG_LEVELS, OFFLINE_MODE
|
||||
from open_webui.env import (
|
||||
SRC_LOG_LEVELS,
|
||||
OFFLINE_MODE,
|
||||
ENABLE_FORWARD_USER_INFO_HEADERS,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
@@ -61,9 +66,7 @@ class VectorSearchRetriever(BaseRetriever):
|
||||
|
||||
|
||||
def query_doc(
|
||||
collection_name: str,
|
||||
query_embedding: list[float],
|
||||
k: int,
|
||||
collection_name: str, query_embedding: list[float], k: int, user: UserModel = None
|
||||
):
|
||||
try:
|
||||
result = VECTOR_DB_CLIENT.search(
|
||||
@@ -259,26 +262,31 @@ def get_embedding_function(
|
||||
embedding_batch_size,
|
||||
):
|
||||
if embedding_engine == "":
|
||||
return lambda query: embedding_function.encode(query).tolist()
|
||||
return lambda query, user=None: embedding_function.encode(query).tolist()
|
||||
elif embedding_engine in ["ollama", "openai"]:
|
||||
func = lambda query: generate_embeddings(
|
||||
func = lambda query, user=None: generate_embeddings(
|
||||
engine=embedding_engine,
|
||||
model=embedding_model,
|
||||
text=query,
|
||||
url=url,
|
||||
key=key,
|
||||
user=user,
|
||||
)
|
||||
|
||||
def generate_multiple(query, func):
|
||||
def generate_multiple(query, user, func):
|
||||
if isinstance(query, list):
|
||||
embeddings = []
|
||||
for i in range(0, len(query), embedding_batch_size):
|
||||
embeddings.extend(func(query[i : i + embedding_batch_size]))
|
||||
embeddings.extend(
|
||||
func(query[i : i + embedding_batch_size], user=user)
|
||||
)
|
||||
return embeddings
|
||||
else:
|
||||
return func(query)
|
||||
return func(query, user)
|
||||
|
||||
return lambda query: generate_multiple(query, func)
|
||||
return lambda query, user=None: generate_multiple(query, user, func)
|
||||
else:
|
||||
raise ValueError(f"Unknown embedding engine: {embedding_engine}")
|
||||
|
||||
|
||||
def get_sources_from_files(
|
||||
@@ -423,7 +431,11 @@ def get_model_path(model: str, update_model: bool = False):
|
||||
|
||||
|
||||
def generate_openai_batch_embeddings(
|
||||
model: str, texts: list[str], url: str = "https://api.openai.com/v1", key: str = ""
|
||||
model: str,
|
||||
texts: list[str],
|
||||
url: str = "https://api.openai.com/v1",
|
||||
key: str = "",
|
||||
user: UserModel = None,
|
||||
) -> Optional[list[list[float]]]:
|
||||
try:
|
||||
r = requests.post(
|
||||
@@ -431,6 +443,16 @@ def generate_openai_batch_embeddings(
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {key}",
|
||||
**(
|
||||
{
|
||||
"X-OpenWebUI-User-Name": user.name,
|
||||
"X-OpenWebUI-User-Id": user.id,
|
||||
"X-OpenWebUI-User-Email": user.email,
|
||||
"X-OpenWebUI-User-Role": user.role,
|
||||
}
|
||||
if ENABLE_FORWARD_USER_INFO_HEADERS and user
|
||||
else {}
|
||||
),
|
||||
},
|
||||
json={"input": texts, "model": model},
|
||||
)
|
||||
@@ -446,7 +468,7 @@ def generate_openai_batch_embeddings(
|
||||
|
||||
|
||||
def generate_ollama_batch_embeddings(
|
||||
model: str, texts: list[str], url: str, key: str = ""
|
||||
model: str, texts: list[str], url: str, key: str = "", user: UserModel = None
|
||||
) -> Optional[list[list[float]]]:
|
||||
try:
|
||||
r = requests.post(
|
||||
@@ -454,6 +476,16 @@ def generate_ollama_batch_embeddings(
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {key}",
|
||||
**(
|
||||
{
|
||||
"X-OpenWebUI-User-Name": user.name,
|
||||
"X-OpenWebUI-User-Id": user.id,
|
||||
"X-OpenWebUI-User-Email": user.email,
|
||||
"X-OpenWebUI-User-Role": user.role,
|
||||
}
|
||||
if ENABLE_FORWARD_USER_INFO_HEADERS
|
||||
else {}
|
||||
),
|
||||
},
|
||||
json={"input": texts, "model": model},
|
||||
)
|
||||
@@ -472,22 +504,29 @@ def generate_ollama_batch_embeddings(
|
||||
def generate_embeddings(engine: str, model: str, text: Union[str, list[str]], **kwargs):
|
||||
url = kwargs.get("url", "")
|
||||
key = kwargs.get("key", "")
|
||||
user = kwargs.get("user")
|
||||
|
||||
if engine == "ollama":
|
||||
if isinstance(text, list):
|
||||
embeddings = generate_ollama_batch_embeddings(
|
||||
**{"model": model, "texts": text, "url": url, "key": key}
|
||||
**{"model": model, "texts": text, "url": url, "key": key, "user": user}
|
||||
)
|
||||
else:
|
||||
embeddings = generate_ollama_batch_embeddings(
|
||||
**{"model": model, "texts": [text], "url": url, "key": key}
|
||||
**{
|
||||
"model": model,
|
||||
"texts": [text],
|
||||
"url": url,
|
||||
"key": key,
|
||||
"user": user,
|
||||
}
|
||||
)
|
||||
return embeddings[0] if isinstance(text, str) else embeddings
|
||||
elif engine == "openai":
|
||||
if isinstance(text, list):
|
||||
embeddings = generate_openai_batch_embeddings(model, text, url, key)
|
||||
embeddings = generate_openai_batch_embeddings(model, text, url, key, user)
|
||||
else:
|
||||
embeddings = generate_openai_batch_embeddings(model, [text], url, key)
|
||||
embeddings = generate_openai_batch_embeddings(model, [text], url, key, user)
|
||||
|
||||
return embeddings[0] if isinstance(text, str) else embeddings
|
||||
|
||||
|
||||
@@ -8,13 +8,17 @@ from open_webui.retrieval.vector.main import VectorItem, SearchResult, GetResult
|
||||
from open_webui.config import (
|
||||
MILVUS_URI,
|
||||
MILVUS_DB,
|
||||
MILVUS_TOKEN,
|
||||
)
|
||||
|
||||
|
||||
class MilvusClient:
|
||||
def __init__(self):
|
||||
self.collection_prefix = "open_webui"
|
||||
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB)
|
||||
if MILVUS_TOKEN is None:
|
||||
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB)
|
||||
else:
|
||||
self.client = Client(uri=MILVUS_URI, database=MILVUS_DB, token=MILVUS_TOKEN)
|
||||
|
||||
def _result_to_get_result(self, result) -> GetResult:
|
||||
ids = []
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from open_webui.env import SRC_LOG_LEVELS
|
||||
from open_webui.retrieval.web.main import SearchResult
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
|
||||
EXA_API_BASE = "https://api.exa.ai"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExaResult:
|
||||
url: str
|
||||
title: str
|
||||
text: str
|
||||
|
||||
|
||||
def search_exa(
|
||||
api_key: str,
|
||||
query: str,
|
||||
count: int,
|
||||
filter_list: Optional[list[str]] = None,
|
||||
) -> list[SearchResult]:
|
||||
"""Search using Exa Search API and return the results as a list of SearchResult objects.
|
||||
|
||||
Args:
|
||||
api_key (str): A Exa Search API key
|
||||
query (str): The query to search for
|
||||
count (int): Number of results to return
|
||||
filter_list (Optional[list[str]]): List of domains to filter results by
|
||||
"""
|
||||
log.info(f"Searching with Exa for query: {query}")
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||
|
||||
payload = {
|
||||
"query": query,
|
||||
"numResults": count or 5,
|
||||
"includeDomains": filter_list,
|
||||
"contents": {"text": True, "highlights": True},
|
||||
"type": "auto", # Use the auto search type (keyword or neural)
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{EXA_API_BASE}/search", headers=headers, json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
results = []
|
||||
for result in data["results"]:
|
||||
results.append(
|
||||
ExaResult(
|
||||
url=result["url"],
|
||||
title=result["title"],
|
||||
text=result["text"],
|
||||
)
|
||||
)
|
||||
|
||||
log.info(f"Found {len(results)} results")
|
||||
return [
|
||||
SearchResult(
|
||||
link=result.url,
|
||||
title=result.title,
|
||||
snippet=result.text,
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
except Exception as e:
|
||||
log.error(f"Error searching Exa: {e}")
|
||||
return []
|
||||
@@ -1,3 +1,5 @@
|
||||
import validators
|
||||
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -10,6 +12,8 @@ def get_filtered_results(results, filter_list):
|
||||
filtered_results = []
|
||||
for result in results:
|
||||
url = result.get("url") or result.get("link", "")
|
||||
if not validators.url(url):
|
||||
continue
|
||||
domain = urlparse(url).netloc
|
||||
if any(domain.endswith(filtered_domain) for filtered_domain in filter_list):
|
||||
filtered_results.append(result)
|
||||
|
||||
@@ -43,6 +43,17 @@ def validate_url(url: Union[str, Sequence[str]]):
|
||||
return False
|
||||
|
||||
|
||||
def safe_validate_urls(url: Sequence[str]) -> Sequence[str]:
|
||||
valid_urls = []
|
||||
for u in url:
|
||||
try:
|
||||
if validate_url(u):
|
||||
valid_urls.append(u)
|
||||
except ValueError:
|
||||
continue
|
||||
return valid_urls
|
||||
|
||||
|
||||
def resolve_hostname(hostname):
|
||||
# Get address information
|
||||
addr_info = socket.getaddrinfo(hostname, None)
|
||||
@@ -86,11 +97,11 @@ def get_web_loader(
|
||||
verify_ssl: bool = True,
|
||||
requests_per_second: int = 2,
|
||||
):
|
||||
# Check if the URL is valid
|
||||
if not validate_url(urls):
|
||||
raise ValueError(ERROR_MESSAGES.INVALID_URL)
|
||||
# Check if the URLs are valid
|
||||
safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)
|
||||
|
||||
return SafeWebBaseLoader(
|
||||
urls,
|
||||
safe_urls,
|
||||
verify_ssl=verify_ssl,
|
||||
requests_per_second=requests_per_second,
|
||||
continue_on_failure=True,
|
||||
|
||||
@@ -25,8 +25,8 @@ from open_webui.env import (
|
||||
WEBUI_AUTH,
|
||||
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
||||
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
||||
WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
WEBUI_SESSION_COOKIE_SECURE,
|
||||
WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
WEBUI_AUTH_COOKIE_SECURE,
|
||||
SRC_LOG_LEVELS,
|
||||
)
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
@@ -95,8 +95,8 @@ async def get_session_user(
|
||||
value=token,
|
||||
expires=datetime_expires_at,
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
)
|
||||
|
||||
user_permissions = get_permissions(
|
||||
@@ -164,7 +164,7 @@ async def update_password(
|
||||
############################
|
||||
# LDAP Authentication
|
||||
############################
|
||||
@router.post("/ldap", response_model=SigninResponse)
|
||||
@router.post("/ldap", response_model=SessionUserResponse)
|
||||
async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
ENABLE_LDAP = request.app.state.config.ENABLE_LDAP
|
||||
LDAP_SERVER_LABEL = request.app.state.config.LDAP_SERVER_LABEL
|
||||
@@ -288,6 +288,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
)
|
||||
|
||||
user_permissions = get_permissions(
|
||||
user.id, request.app.state.config.USER_PERMISSIONS
|
||||
)
|
||||
|
||||
return {
|
||||
"token": token,
|
||||
"token_type": "Bearer",
|
||||
@@ -296,6 +300,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
"name": user.name,
|
||||
"role": user.role,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"permissions": user_permissions,
|
||||
}
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
@@ -378,8 +383,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
|
||||
value=token,
|
||||
expires=datetime_expires_at,
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
)
|
||||
|
||||
user_permissions = get_permissions(
|
||||
@@ -473,8 +478,8 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
|
||||
value=token,
|
||||
expires=datetime_expires_at,
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
)
|
||||
|
||||
if request.app.state.config.WEBHOOK_URL:
|
||||
|
||||
@@ -444,15 +444,21 @@ async def pin_chat_by_id(id: str, user=Depends(get_verified_user)):
|
||||
############################
|
||||
|
||||
|
||||
class CloneForm(BaseModel):
|
||||
title: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/{id}/clone", response_model=Optional[ChatResponse])
|
||||
async def clone_chat_by_id(id: str, user=Depends(get_verified_user)):
|
||||
async def clone_chat_by_id(
|
||||
form_data: CloneForm, id: str, user=Depends(get_verified_user)
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id)
|
||||
if chat:
|
||||
updated_chat = {
|
||||
**chat.chat,
|
||||
"originalChatId": chat.id,
|
||||
"branchPointMessageId": chat.chat["history"]["currentId"],
|
||||
"title": f"Clone of {chat.title}",
|
||||
"title": form_data.title if form_data.title else f"Clone of {chat.title}",
|
||||
}
|
||||
|
||||
chat = Chats.insert_new_chat(user.id, ChatForm(**{"chat": updated_chat}))
|
||||
|
||||
@@ -71,7 +71,7 @@ def upload_file(
|
||||
)
|
||||
|
||||
try:
|
||||
process_file(request, ProcessFileForm(file_id=id))
|
||||
process_file(request, ProcessFileForm(file_id=id), user=user)
|
||||
file_item = Files.get_file_by_id(id=id)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -193,7 +193,9 @@ async def update_file_data_content_by_id(
|
||||
if file and (file.user_id == user.id or user.role == "admin"):
|
||||
try:
|
||||
process_file(
|
||||
request, ProcessFileForm(file_id=id, content=form_data.content)
|
||||
request,
|
||||
ProcessFileForm(file_id=id, content=form_data.content),
|
||||
user=user,
|
||||
)
|
||||
file = Files.get_file_by_id(id=id)
|
||||
except Exception as e:
|
||||
|
||||
@@ -264,7 +264,11 @@ def add_file_to_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -285,7 +289,9 @@ def add_file_to_knowledge_by_id(
|
||||
# Add content to the vector database
|
||||
try:
|
||||
process_file(
|
||||
request, ProcessFileForm(file_id=form_data.file_id, collection_name=id)
|
||||
request,
|
||||
ProcessFileForm(file_id=form_data.file_id, collection_name=id),
|
||||
user=user,
|
||||
)
|
||||
except Exception as e:
|
||||
log.debug(e)
|
||||
@@ -342,7 +348,12 @@ def update_file_from_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -363,7 +374,9 @@ def update_file_from_knowledge_by_id(
|
||||
# Add content to the vector database
|
||||
try:
|
||||
process_file(
|
||||
request, ProcessFileForm(file_id=form_data.file_id, collection_name=id)
|
||||
request,
|
||||
ProcessFileForm(file_id=form_data.file_id, collection_name=id),
|
||||
user=user,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -406,7 +419,11 @@ def remove_file_from_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -429,10 +446,6 @@ def remove_file_from_knowledge_by_id(
|
||||
if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection):
|
||||
VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection)
|
||||
|
||||
# Delete physical file
|
||||
if file.path:
|
||||
Storage.delete_file(file.path)
|
||||
|
||||
# Delete file from database
|
||||
Files.delete_file_by_id(form_data.file_id)
|
||||
|
||||
@@ -484,7 +497,11 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user)):
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -543,7 +560,11 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user)):
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -582,7 +603,11 @@ def add_files_to_knowledge_batch(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if knowledge.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not has_access(user.id, "write", knowledge.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
|
||||
@@ -57,7 +57,7 @@ async def add_memory(
|
||||
{
|
||||
"id": memory.id,
|
||||
"text": memory.content,
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content, user),
|
||||
"metadata": {"created_at": memory.created_at},
|
||||
}
|
||||
],
|
||||
@@ -82,7 +82,7 @@ async def query_memory(
|
||||
):
|
||||
results = VECTOR_DB_CLIENT.search(
|
||||
collection_name=f"user-memory-{user.id}",
|
||||
vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content)],
|
||||
vectors=[request.app.state.EMBEDDING_FUNCTION(form_data.content, user)],
|
||||
limit=form_data.k,
|
||||
)
|
||||
|
||||
@@ -105,7 +105,7 @@ async def reset_memory_from_vector_db(
|
||||
{
|
||||
"id": memory.id,
|
||||
"text": memory.content,
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content, user),
|
||||
"metadata": {
|
||||
"created_at": memory.created_at,
|
||||
"updated_at": memory.updated_at,
|
||||
@@ -160,7 +160,9 @@ async def update_memory_by_id(
|
||||
{
|
||||
"id": memory.id,
|
||||
"text": memory.content,
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(memory.content),
|
||||
"vector": request.app.state.EMBEDDING_FUNCTION(
|
||||
memory.content, user
|
||||
),
|
||||
"metadata": {
|
||||
"created_at": memory.created_at,
|
||||
"updated_at": memory.updated_at,
|
||||
|
||||
@@ -155,6 +155,16 @@ async def update_model_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
model.user_id != user.id
|
||||
and not has_access(user.id, "write", model.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
model = Models.update_model_by_id(id, form_data)
|
||||
return model
|
||||
|
||||
@@ -173,7 +183,11 @@ async def delete_model_by_id(id: str, user=Depends(get_verified_user)):
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if model.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
user.role != "admin"
|
||||
and model.user_id != user.id
|
||||
and not has_access(user.id, "write", model.access_control)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
|
||||
@@ -261,7 +261,7 @@ async def get_all_models(request: Request):
|
||||
if request.app.state.config.ENABLE_OLLAMA_API:
|
||||
request_tasks = []
|
||||
for idx, url in enumerate(request.app.state.config.OLLAMA_BASE_URLS):
|
||||
if (str(idx) not in request.app.state.config.OLLAMA_API_CONFIGS) or (
|
||||
if (str(idx) not in request.app.state.config.OLLAMA_API_CONFIGS) and (
|
||||
url not in request.app.state.config.OLLAMA_API_CONFIGS # Legacy support
|
||||
):
|
||||
request_tasks.append(send_get_request(f"{url}/api/tags"))
|
||||
@@ -395,7 +395,7 @@ async def get_ollama_tags(
|
||||
)
|
||||
|
||||
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
|
||||
models["models"] = get_filtered_models(models, user)
|
||||
models["models"] = await get_filtered_models(models, user)
|
||||
|
||||
return models
|
||||
|
||||
@@ -551,11 +551,12 @@ async def push_model(
|
||||
|
||||
|
||||
class CreateModelForm(BaseModel):
|
||||
name: str
|
||||
modelfile: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
stream: Optional[bool] = None
|
||||
path: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
@router.post("/api/create")
|
||||
@router.post("/api/create/{url_idx}")
|
||||
@@ -938,6 +939,7 @@ async def generate_completion(
|
||||
class ChatMessage(BaseModel):
|
||||
role: str
|
||||
content: str
|
||||
tool_calls: Optional[list[dict]] = None
|
||||
images: Optional[list[str]] = None
|
||||
|
||||
|
||||
@@ -949,6 +951,7 @@ class GenerateChatCompletionForm(BaseModel):
|
||||
template: Optional[str] = None
|
||||
stream: Optional[bool] = True
|
||||
keep_alive: Optional[Union[int, str]] = None
|
||||
tools: Optional[list[dict]] = None
|
||||
|
||||
|
||||
async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] = None):
|
||||
@@ -961,7 +964,7 @@ async def get_ollama_url(request: Request, model: str, url_idx: Optional[int] =
|
||||
)
|
||||
url_idx = random.choice(models[model].get("urls", []))
|
||||
url = request.app.state.config.OLLAMA_BASE_URLS[url_idx]
|
||||
return url
|
||||
return url, url_idx
|
||||
|
||||
|
||||
@router.post("/api/chat")
|
||||
@@ -976,6 +979,7 @@ async def generate_chat_completion(
|
||||
if BYPASS_MODEL_ACCESS_CONTROL:
|
||||
bypass_filter = True
|
||||
|
||||
metadata = form_data.pop("metadata", None)
|
||||
try:
|
||||
form_data = GenerateChatCompletionForm(**form_data)
|
||||
except Exception as e:
|
||||
@@ -986,8 +990,6 @@ async def generate_chat_completion(
|
||||
)
|
||||
|
||||
payload = {**form_data.model_dump(exclude_none=True)}
|
||||
if "metadata" in payload:
|
||||
del payload["metadata"]
|
||||
|
||||
model_id = payload["model"]
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
@@ -1005,7 +1007,7 @@ async def generate_chat_completion(
|
||||
payload["options"] = apply_model_params_to_body_ollama(
|
||||
params, payload["options"]
|
||||
)
|
||||
payload = apply_model_system_prompt_to_body(params, payload, user)
|
||||
payload = apply_model_system_prompt_to_body(params, payload, metadata)
|
||||
|
||||
# Check if user has access to the model
|
||||
if not bypass_filter and user.role == "user":
|
||||
@@ -1029,7 +1031,7 @@ async def generate_chat_completion(
|
||||
if ":" not in payload["model"]:
|
||||
payload["model"] = f"{payload['model']}:latest"
|
||||
|
||||
url = await get_ollama_url(request, payload["model"], url_idx)
|
||||
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
str(url_idx),
|
||||
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
|
||||
@@ -1131,7 +1133,7 @@ async def generate_openai_completion(
|
||||
if ":" not in payload["model"]:
|
||||
payload["model"] = f"{payload['model']}:latest"
|
||||
|
||||
url = await get_ollama_url(request, payload["model"], url_idx)
|
||||
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
str(url_idx),
|
||||
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
|
||||
@@ -1208,7 +1210,7 @@ async def generate_openai_chat_completion(
|
||||
if ":" not in payload["model"]:
|
||||
payload["model"] = f"{payload['model']}:latest"
|
||||
|
||||
url = await get_ollama_url(request, payload["model"], url_idx)
|
||||
url, url_idx = await get_ollama_url(request, payload["model"], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
str(url_idx),
|
||||
request.app.state.config.OLLAMA_API_CONFIGS.get(url, {}), # Legacy support
|
||||
|
||||
@@ -266,7 +266,7 @@ async def get_all_models_responses(request: Request) -> list:
|
||||
|
||||
request_tasks = []
|
||||
for idx, url in enumerate(request.app.state.config.OPENAI_API_BASE_URLS):
|
||||
if (str(idx) not in request.app.state.config.OPENAI_API_CONFIGS) or (
|
||||
if (str(idx) not in request.app.state.config.OPENAI_API_CONFIGS) and (
|
||||
url not in request.app.state.config.OPENAI_API_CONFIGS # Legacy support
|
||||
):
|
||||
request_tasks.append(
|
||||
@@ -489,7 +489,7 @@ async def get_models(
|
||||
raise HTTPException(status_code=500, detail=error_detail)
|
||||
|
||||
if user.role == "user" and not BYPASS_MODEL_ACCESS_CONTROL:
|
||||
models["data"] = get_filtered_models(models, user)
|
||||
models["data"] = await get_filtered_models(models, user)
|
||||
|
||||
return models
|
||||
|
||||
@@ -551,9 +551,9 @@ async def generate_chat_completion(
|
||||
bypass_filter = True
|
||||
|
||||
idx = 0
|
||||
|
||||
payload = {**form_data}
|
||||
if "metadata" in payload:
|
||||
del payload["metadata"]
|
||||
metadata = payload.pop("metadata", None)
|
||||
|
||||
model_id = form_data.get("model")
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
@@ -566,7 +566,7 @@ async def generate_chat_completion(
|
||||
|
||||
params = model_info.params.model_dump()
|
||||
payload = apply_model_params_to_body_openai(params, payload)
|
||||
payload = apply_model_system_prompt_to_body(params, payload, user)
|
||||
payload = apply_model_system_prompt_to_body(params, payload, metadata)
|
||||
|
||||
# Check if user has access to the model
|
||||
if not bypass_filter and user.role == "user":
|
||||
|
||||
@@ -147,7 +147,11 @@ async def delete_prompt_by_command(command: str, user=Depends(get_verified_user)
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if prompt.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not has_access(user.id, "write", prompt.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
|
||||
@@ -55,6 +55,7 @@ from open_webui.retrieval.web.serply import search_serply
|
||||
from open_webui.retrieval.web.serpstack import search_serpstack
|
||||
from open_webui.retrieval.web.tavily import search_tavily
|
||||
from open_webui.retrieval.web.bing import search_bing
|
||||
from open_webui.retrieval.web.exa import search_exa
|
||||
|
||||
|
||||
from open_webui.retrieval.utils import (
|
||||
@@ -388,6 +389,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
||||
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
||||
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
||||
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
|
||||
"exa_api_key": request.app.state.config.EXA_API_KEY,
|
||||
"result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
|
||||
"concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
|
||||
},
|
||||
@@ -436,6 +438,7 @@ class WebSearchConfig(BaseModel):
|
||||
jina_api_key: Optional[str] = None
|
||||
bing_search_v7_endpoint: Optional[str] = None
|
||||
bing_search_v7_subscription_key: Optional[str] = None
|
||||
exa_api_key: Optional[str] = None
|
||||
result_count: Optional[int] = None
|
||||
concurrent_requests: Optional[int] = None
|
||||
|
||||
@@ -542,6 +545,8 @@ async def update_rag_config(
|
||||
form_data.web.search.bing_search_v7_subscription_key
|
||||
)
|
||||
|
||||
request.app.state.config.EXA_API_KEY = form_data.web.search.exa_api_key
|
||||
|
||||
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = (
|
||||
form_data.web.search.result_count
|
||||
)
|
||||
@@ -591,6 +596,7 @@ async def update_rag_config(
|
||||
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
||||
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
||||
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
|
||||
"exa_api_key": request.app.state.config.EXA_API_KEY,
|
||||
"result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
|
||||
"concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
|
||||
},
|
||||
@@ -660,6 +666,7 @@ def save_docs_to_vector_db(
|
||||
overwrite: bool = False,
|
||||
split: bool = True,
|
||||
add: bool = False,
|
||||
user=None,
|
||||
) -> bool:
|
||||
def _get_docs_info(docs: list[Document]) -> str:
|
||||
docs_info = set()
|
||||
@@ -775,7 +782,7 @@ def save_docs_to_vector_db(
|
||||
)
|
||||
|
||||
embeddings = embedding_function(
|
||||
list(map(lambda x: x.replace("\n", " "), texts))
|
||||
list(map(lambda x: x.replace("\n", " "), texts)), user=user
|
||||
)
|
||||
|
||||
items = [
|
||||
@@ -933,6 +940,7 @@ def process_file(
|
||||
"hash": hash,
|
||||
},
|
||||
add=(True if form_data.collection_name else False),
|
||||
user=user,
|
||||
)
|
||||
|
||||
if result:
|
||||
@@ -990,7 +998,7 @@ def process_text(
|
||||
text_content = form_data.content
|
||||
log.debug(f"text_content: {text_content}")
|
||||
|
||||
result = save_docs_to_vector_db(request, docs, collection_name)
|
||||
result = save_docs_to_vector_db(request, docs, collection_name, user=user)
|
||||
if result:
|
||||
return {
|
||||
"status": True,
|
||||
@@ -1023,7 +1031,9 @@ def process_youtube_video(
|
||||
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)
|
||||
save_docs_to_vector_db(
|
||||
request, docs, collection_name, overwrite=True, user=user
|
||||
)
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
@@ -1064,7 +1074,9 @@ def process_web(
|
||||
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)
|
||||
save_docs_to_vector_db(
|
||||
request, docs, collection_name, overwrite=True, user=user
|
||||
)
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
@@ -1099,6 +1111,7 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
|
||||
- SERPER_API_KEY
|
||||
- SERPLY_API_KEY
|
||||
- TAVILY_API_KEY
|
||||
- EXA_API_KEY
|
||||
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
|
||||
Args:
|
||||
query (str): The query to search for
|
||||
@@ -1233,6 +1246,13 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
|
||||
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
|
||||
request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
|
||||
)
|
||||
elif engine == "exa":
|
||||
return search_exa(
|
||||
request.app.state.config.EXA_API_KEY,
|
||||
query,
|
||||
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
|
||||
request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
|
||||
)
|
||||
else:
|
||||
raise Exception("No search engine API key found in environment variables")
|
||||
|
||||
@@ -1272,7 +1292,9 @@ def process_web_search(
|
||||
requests_per_second=request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
|
||||
)
|
||||
docs = loader.load()
|
||||
save_docs_to_vector_db(request, docs, collection_name, overwrite=True)
|
||||
save_docs_to_vector_db(
|
||||
request, docs, collection_name, overwrite=True, user=user
|
||||
)
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
@@ -1306,7 +1328,9 @@ def query_doc_handler(
|
||||
return query_doc_with_hybrid_search(
|
||||
collection_name=form_data.collection_name,
|
||||
query=form_data.query,
|
||||
embedding_function=request.app.state.EMBEDDING_FUNCTION,
|
||||
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
|
||||
query, user=user
|
||||
),
|
||||
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
|
||||
reranking_function=request.app.state.rf,
|
||||
r=(
|
||||
@@ -1314,12 +1338,16 @@ def query_doc_handler(
|
||||
if form_data.r
|
||||
else request.app.state.config.RELEVANCE_THRESHOLD
|
||||
),
|
||||
user=user,
|
||||
)
|
||||
else:
|
||||
return query_doc(
|
||||
collection_name=form_data.collection_name,
|
||||
query_embedding=request.app.state.EMBEDDING_FUNCTION(form_data.query),
|
||||
query_embedding=request.app.state.EMBEDDING_FUNCTION(
|
||||
form_data.query, user=user
|
||||
),
|
||||
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
|
||||
user=user,
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -1348,7 +1376,9 @@ def query_collection_handler(
|
||||
return query_collection_with_hybrid_search(
|
||||
collection_names=form_data.collection_names,
|
||||
queries=[form_data.query],
|
||||
embedding_function=request.app.state.EMBEDDING_FUNCTION,
|
||||
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
|
||||
query, user=user
|
||||
),
|
||||
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
|
||||
reranking_function=request.app.state.rf,
|
||||
r=(
|
||||
@@ -1361,7 +1391,9 @@ def query_collection_handler(
|
||||
return query_collection(
|
||||
collection_names=form_data.collection_names,
|
||||
queries=[form_data.query],
|
||||
embedding_function=request.app.state.EMBEDDING_FUNCTION,
|
||||
embedding_function=lambda query: request.app.state.EMBEDDING_FUNCTION(
|
||||
query, user=user
|
||||
),
|
||||
k=form_data.k if form_data.k else request.app.state.config.TOP_K,
|
||||
)
|
||||
|
||||
@@ -1509,6 +1541,7 @@ def process_files_batch(
|
||||
docs=all_docs,
|
||||
collection_name=collection_name,
|
||||
add=True,
|
||||
user=user,
|
||||
)
|
||||
|
||||
# Update all files with collection name
|
||||
|
||||
@@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import logging
|
||||
import re
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.utils.task import (
|
||||
@@ -89,6 +90,10 @@ async def update_task_config(
|
||||
form_data.TITLE_GENERATION_PROMPT_TEMPLATE
|
||||
)
|
||||
|
||||
request.app.state.config.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE = (
|
||||
form_data.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE
|
||||
)
|
||||
|
||||
request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = (
|
||||
form_data.ENABLE_AUTOCOMPLETE_GENERATION
|
||||
)
|
||||
@@ -161,9 +166,20 @@ async def generate_title(
|
||||
else:
|
||||
template = DEFAULT_TITLE_GENERATION_PROMPT_TEMPLATE
|
||||
|
||||
messages = form_data["messages"]
|
||||
|
||||
# Remove reasoning details from the messages
|
||||
for message in messages:
|
||||
message["content"] = re.sub(
|
||||
r"<details\s+type=\"reasoning\"[^>]*>.*?<\/details>",
|
||||
"",
|
||||
message["content"],
|
||||
flags=re.S,
|
||||
).strip()
|
||||
|
||||
content = title_generation_template(
|
||||
template,
|
||||
form_data["messages"],
|
||||
messages,
|
||||
{
|
||||
"name": user.name,
|
||||
"location": user.info.get("location") if user.info else None,
|
||||
@@ -175,10 +191,10 @@ async def generate_title(
|
||||
"messages": [{"role": "user", "content": content}],
|
||||
"stream": False,
|
||||
**(
|
||||
{"max_tokens": 50}
|
||||
{"max_tokens": 1000}
|
||||
if models[task_model_id]["owned_by"] == "ollama"
|
||||
else {
|
||||
"max_completion_tokens": 50,
|
||||
"max_completion_tokens": 1000,
|
||||
}
|
||||
),
|
||||
"metadata": {
|
||||
|
||||
@@ -227,7 +227,11 @@ async def delete_tools_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if tools.user_id != user.id and user.role != "admin":
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not has_access(user.id, "write", tools.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
@@ -309,6 +313,17 @@ async def update_tools_valves_by_id(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not has_access(user.id, "write", tools.access_control)
|
||||
and user.role != "admin"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
if id in request.app.state.TOOLS:
|
||||
tools_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
|
||||
@@ -79,6 +79,7 @@ class ChatPermissions(BaseModel):
|
||||
class FeaturesPermissions(BaseModel):
|
||||
web_search: bool = True
|
||||
image_generation: bool = True
|
||||
code_interpreter: bool = True
|
||||
|
||||
|
||||
class UserPermissions(BaseModel):
|
||||
|
||||
@@ -325,7 +325,7 @@ def get_event_emitter(request_info):
|
||||
|
||||
|
||||
def get_event_call(request_info):
|
||||
async def __event_call__(event_data):
|
||||
async def __event_caller__(event_data):
|
||||
response = await sio.call(
|
||||
"chat-events",
|
||||
{
|
||||
@@ -337,7 +337,10 @@ def get_event_call(request_info):
|
||||
)
|
||||
return response
|
||||
|
||||
return __event_call__
|
||||
return __event_caller__
|
||||
|
||||
|
||||
get_event_caller = get_event_call
|
||||
|
||||
|
||||
def get_user_id_from_session_pool(sid):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,18 @@ from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
|
||||
import collections.abc
|
||||
|
||||
|
||||
def deep_update(d, u):
|
||||
for k, v in u.items():
|
||||
if isinstance(v, collections.abc.Mapping):
|
||||
d[k] = deep_update(d.get(k, {}), v)
|
||||
else:
|
||||
d[k] = v
|
||||
return d
|
||||
|
||||
|
||||
def get_message_list(messages, message_id):
|
||||
"""
|
||||
Reconstructs a list of messages in order up to the specified message_id.
|
||||
@@ -20,7 +32,7 @@ def get_message_list(messages, message_id):
|
||||
current_message = messages.get(message_id)
|
||||
|
||||
if not current_message:
|
||||
return f"Message ID {message_id} not found in the history."
|
||||
return None
|
||||
|
||||
# Reconstruct the chain by following the parentId links
|
||||
message_list = []
|
||||
@@ -131,6 +143,44 @@ def add_or_update_system_message(content: str, messages: list[dict]):
|
||||
return messages
|
||||
|
||||
|
||||
def add_or_update_user_message(content: str, messages: list[dict]):
|
||||
"""
|
||||
Adds a new user message at the end of the messages list
|
||||
or updates the existing user message at the end.
|
||||
|
||||
:param msg: The message to be added or appended.
|
||||
:param messages: The list of message dictionaries.
|
||||
:return: The updated list of message dictionaries.
|
||||
"""
|
||||
|
||||
if messages and messages[-1].get("role") == "user":
|
||||
messages[-1]["content"] = f"{messages[-1]['content']}\n{content}"
|
||||
else:
|
||||
# Insert at the end
|
||||
messages.append({"role": "user", "content": content})
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def append_or_update_assistant_message(content: str, messages: list[dict]):
|
||||
"""
|
||||
Adds a new assistant message at the end of the messages list
|
||||
or updates the existing assistant message at the end.
|
||||
|
||||
:param msg: The message to be added or appended.
|
||||
:param messages: The list of message dictionaries.
|
||||
:return: The updated list of message dictionaries.
|
||||
"""
|
||||
|
||||
if messages and messages[-1].get("role") == "assistant":
|
||||
messages[-1]["content"] = f"{messages[-1]['content']}\n{content}"
|
||||
else:
|
||||
# Insert at the end
|
||||
messages.append({"role": "assistant", "content": content})
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def openai_chat_message_template(model: str):
|
||||
return {
|
||||
"id": f"{model}-{str(uuid.uuid4())}",
|
||||
@@ -141,13 +191,24 @@ def openai_chat_message_template(model: str):
|
||||
|
||||
|
||||
def openai_chat_chunk_message_template(
|
||||
model: str, message: Optional[str] = None, usage: Optional[dict] = None
|
||||
model: str,
|
||||
content: Optional[str] = None,
|
||||
tool_calls: Optional[list[dict]] = None,
|
||||
usage: Optional[dict] = None,
|
||||
) -> dict:
|
||||
template = openai_chat_message_template(model)
|
||||
template["object"] = "chat.completion.chunk"
|
||||
if message:
|
||||
template["choices"][0]["delta"] = {"content": message}
|
||||
else:
|
||||
|
||||
template["choices"][0]["index"] = 0
|
||||
template["choices"][0]["delta"] = {}
|
||||
|
||||
if content:
|
||||
template["choices"][0]["delta"]["content"] = content
|
||||
|
||||
if tool_calls:
|
||||
template["choices"][0]["delta"]["tool_calls"] = tool_calls
|
||||
|
||||
if not content and not tool_calls:
|
||||
template["choices"][0]["finish_reason"] = "stop"
|
||||
|
||||
if usage:
|
||||
|
||||
@@ -35,7 +35,7 @@ from open_webui.config import (
|
||||
AppConfig,
|
||||
)
|
||||
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
||||
from open_webui.env import WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE
|
||||
from open_webui.env import WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE
|
||||
from open_webui.utils.misc import parse_duration
|
||||
from open_webui.utils.auth import get_password_hash, create_token
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
@@ -82,7 +82,8 @@ class OAuthManager:
|
||||
oauth_allowed_roles = auth_manager_config.OAUTH_ALLOWED_ROLES
|
||||
oauth_admin_roles = auth_manager_config.OAUTH_ADMIN_ROLES
|
||||
oauth_roles = None
|
||||
role = "pending" # Default/fallback role if no matching roles are found
|
||||
# Default/fallback role if no matching roles are found
|
||||
role = auth_manager_config.DEFAULT_USER_ROLE
|
||||
|
||||
# Next block extracts the roles from the user data, accepting nested claims of any depth
|
||||
if oauth_claim and oauth_allowed_roles and oauth_admin_roles:
|
||||
@@ -273,11 +274,16 @@ class OAuthManager:
|
||||
log.error(
|
||||
f"Error downloading profile image '{picture_url}': {e}"
|
||||
)
|
||||
picture_url = ""
|
||||
picture_url = "/user.png"
|
||||
if not picture_url:
|
||||
picture_url = "/user.png"
|
||||
|
||||
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
|
||||
|
||||
name = user_data.get(username_claim)
|
||||
if not isinstance(user, str):
|
||||
name = email
|
||||
|
||||
role = self.get_user_role(None, user_data)
|
||||
|
||||
user = Auths.insert_new_auth(
|
||||
@@ -285,7 +291,7 @@ class OAuthManager:
|
||||
password=get_password_hash(
|
||||
str(uuid.uuid4())
|
||||
), # Random password, not used
|
||||
name=user_data.get(username_claim, "User"),
|
||||
name=name,
|
||||
profile_image_url=picture_url,
|
||||
role=role,
|
||||
oauth_sub=provider_sub,
|
||||
@@ -323,8 +329,8 @@ class OAuthManager:
|
||||
key="token",
|
||||
value=jwt_token,
|
||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
)
|
||||
|
||||
if ENABLE_OAUTH_SIGNUP.value:
|
||||
@@ -333,8 +339,8 @@ class OAuthManager:
|
||||
key="oauth_id_token",
|
||||
value=oauth_id_token,
|
||||
httponly=True,
|
||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
||||
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||
)
|
||||
# Redirect back to the frontend with the JWT token
|
||||
redirect_url = f"{request.base_url}auth#token={jwt_token}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from open_webui.utils.task import prompt_template
|
||||
from open_webui.utils.task import prompt_variables_template
|
||||
from open_webui.utils.misc import (
|
||||
add_or_update_system_message,
|
||||
)
|
||||
@@ -7,19 +7,18 @@ from typing import Callable, Optional
|
||||
|
||||
|
||||
# inplace function: form_data is modified
|
||||
def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict:
|
||||
def apply_model_system_prompt_to_body(
|
||||
params: dict, form_data: dict, metadata: Optional[dict] = None
|
||||
) -> dict:
|
||||
system = params.get("system", None)
|
||||
if not system:
|
||||
return form_data
|
||||
|
||||
if user:
|
||||
template_params = {
|
||||
"user_name": user.name,
|
||||
"user_location": user.info.get("location") if user.info else None,
|
||||
}
|
||||
else:
|
||||
template_params = {}
|
||||
system = prompt_template(system, **template_params)
|
||||
if metadata:
|
||||
print("apply_model_system_prompt_to_body: metadata", metadata)
|
||||
variables = metadata.get("variables", {})
|
||||
system = prompt_variables_template(system, variables)
|
||||
|
||||
form_data["messages"] = add_or_update_system_message(
|
||||
system, form_data.get("messages", [])
|
||||
)
|
||||
@@ -47,6 +46,7 @@ def apply_model_params_to_body_openai(params: dict, form_data: dict) -> dict:
|
||||
"top_p": float,
|
||||
"max_tokens": int,
|
||||
"frequency_penalty": float,
|
||||
"reasoning_effort": str,
|
||||
"seed": lambda x: x,
|
||||
"stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
|
||||
}
|
||||
@@ -154,6 +154,9 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
|
||||
)
|
||||
ollama_payload["stream"] = openai_payload.get("stream", False)
|
||||
|
||||
if "tools" in openai_payload:
|
||||
ollama_payload["tools"] = openai_payload["tools"]
|
||||
|
||||
if "format" in openai_payload:
|
||||
ollama_payload["format"] = openai_payload["format"]
|
||||
|
||||
@@ -187,4 +190,7 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
|
||||
if ollama_options:
|
||||
ollama_payload["options"] = ollama_options
|
||||
|
||||
if "metadata" in openai_payload:
|
||||
ollama_payload["metadata"] = openai_payload["metadata"]
|
||||
|
||||
return ollama_payload
|
||||
|
||||
@@ -167,9 +167,14 @@ def load_function_module_by_id(function_id, content=None):
|
||||
|
||||
def install_frontmatter_requirements(requirements):
|
||||
if requirements:
|
||||
req_list = [req.strip() for req in requirements.split(",")]
|
||||
for req in req_list:
|
||||
log.info(f"Installing requirement: {req}")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
|
||||
try:
|
||||
req_list = [req.strip() for req in requirements.split(",")]
|
||||
for req in req_list:
|
||||
log.info(f"Installing requirement: {req}")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
|
||||
except Exception as e:
|
||||
log.error(f"Error installing package: {req}")
|
||||
raise e
|
||||
|
||||
else:
|
||||
log.info("No requirements found in frontmatter.")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from uuid import uuid4
|
||||
from open_webui.utils.misc import (
|
||||
openai_chat_chunk_message_template,
|
||||
openai_chat_completion_message_template,
|
||||
@@ -9,7 +10,48 @@ def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
|
||||
model = ollama_response.get("model", "ollama")
|
||||
message_content = ollama_response.get("message", {}).get("content", "")
|
||||
|
||||
response = openai_chat_completion_message_template(model, message_content)
|
||||
data = ollama_response
|
||||
usage = {
|
||||
"response_token/s": (
|
||||
round(
|
||||
(
|
||||
(
|
||||
data.get("eval_count", 0)
|
||||
/ ((data.get("eval_duration", 0) / 10_000_000))
|
||||
)
|
||||
* 100
|
||||
),
|
||||
2,
|
||||
)
|
||||
if data.get("eval_duration", 0) > 0
|
||||
else "N/A"
|
||||
),
|
||||
"prompt_token/s": (
|
||||
round(
|
||||
(
|
||||
(
|
||||
data.get("prompt_eval_count", 0)
|
||||
/ ((data.get("prompt_eval_duration", 0) / 10_000_000))
|
||||
)
|
||||
* 100
|
||||
),
|
||||
2,
|
||||
)
|
||||
if data.get("prompt_eval_duration", 0) > 0
|
||||
else "N/A"
|
||||
),
|
||||
"total_duration": data.get("total_duration", 0),
|
||||
"load_duration": data.get("load_duration", 0),
|
||||
"prompt_eval_count": data.get("prompt_eval_count", 0),
|
||||
"prompt_eval_duration": data.get("prompt_eval_duration", 0),
|
||||
"eval_count": data.get("eval_count", 0),
|
||||
"eval_duration": data.get("eval_duration", 0),
|
||||
"approximate_total": (lambda s: f"{s // 3600}h{(s % 3600) // 60}m{s % 60}s")(
|
||||
(data.get("total_duration", 0) or 0) // 1_000_000_000
|
||||
),
|
||||
}
|
||||
|
||||
response = openai_chat_completion_message_template(model, message_content, usage)
|
||||
return response
|
||||
|
||||
|
||||
@@ -19,6 +61,23 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
|
||||
|
||||
model = data.get("model", "ollama")
|
||||
message_content = data.get("message", {}).get("content", "")
|
||||
tool_calls = data.get("message", {}).get("tool_calls", None)
|
||||
openai_tool_calls = None
|
||||
|
||||
if tool_calls:
|
||||
openai_tool_calls = []
|
||||
for tool_call in tool_calls:
|
||||
openai_tool_call = {
|
||||
"index": tool_call.get("index", 0),
|
||||
"id": tool_call.get("id", f"call_{str(uuid4())}"),
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_call.get("function", {}).get("name", ""),
|
||||
"arguments": f"{tool_call.get('function', {}).get('arguments', {})}",
|
||||
},
|
||||
}
|
||||
openai_tool_calls.append(openai_tool_call)
|
||||
|
||||
done = data.get("done", False)
|
||||
|
||||
usage = None
|
||||
@@ -64,7 +123,7 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
|
||||
}
|
||||
|
||||
data = openai_chat_chunk_message_template(
|
||||
model, message_content if not done else None, usage
|
||||
model, message_content if not done else None, openai_tool_calls, usage
|
||||
)
|
||||
|
||||
line = f"data: {json.dumps(data)}\n\n"
|
||||
|
||||
@@ -32,6 +32,12 @@ def get_task_model_id(
|
||||
return task_model_id
|
||||
|
||||
|
||||
def prompt_variables_template(template: str, variables: dict[str, str]) -> str:
|
||||
for variable, value in variables.items():
|
||||
template = template.replace(variable, value)
|
||||
return template
|
||||
|
||||
|
||||
def prompt_template(
|
||||
template: str, user_name: Optional[str] = None, user_location: Optional[str] = None
|
||||
) -> str:
|
||||
|
||||
@@ -61,6 +61,12 @@ def get_tools(
|
||||
)
|
||||
|
||||
for spec in tools.specs:
|
||||
# TODO: Fix hack for OpenAI API
|
||||
# Some times breaks OpenAI but others don't. Leaving the comment
|
||||
for val in spec.get("parameters", {}).get("properties", {}).values():
|
||||
if val["type"] == "str":
|
||||
val["type"] = "string"
|
||||
|
||||
# Remove internal parameters
|
||||
spec["parameters"]["properties"] = {
|
||||
key: val
|
||||
@@ -73,6 +79,13 @@ def get_tools(
|
||||
# convert to function that takes only model params and inserts custom params
|
||||
original_func = getattr(module, function_name)
|
||||
callable = apply_extra_params_to_tool_function(original_func, extra_params)
|
||||
|
||||
if callable.__doc__ and callable.__doc__.strip() != "":
|
||||
s = re.split(":(param|return)", callable.__doc__, 1)
|
||||
spec["description"] = s[0]
|
||||
else:
|
||||
spec["description"] = function_name
|
||||
|
||||
# TODO: This needs to be a pydantic model
|
||||
tool_dict = {
|
||||
"toolkit_id": tool_id,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
fastapi==0.111.0
|
||||
fastapi==0.115.7
|
||||
uvicorn[standard]==0.30.6
|
||||
pydantic==2.9.2
|
||||
python-multipart==0.0.18
|
||||
@@ -11,7 +11,7 @@ python-jose==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
|
||||
requests==2.32.3
|
||||
aiohttp==3.11.8
|
||||
aiohttp==3.11.11
|
||||
async-timeout
|
||||
aiocache
|
||||
aiofiles
|
||||
@@ -57,10 +57,10 @@ einops==0.8.0
|
||||
ftfy==6.2.3
|
||||
pypdf==4.3.1
|
||||
fpdf2==2.8.2
|
||||
pymdown-extensions==10.11.2
|
||||
pymdown-extensions==10.14.2
|
||||
docx2txt==0.8
|
||||
python-pptx==1.0.0
|
||||
unstructured==0.15.9
|
||||
unstructured==0.16.11
|
||||
nltk==3.9.1
|
||||
Markdown==3.7
|
||||
pypandoc==1.13
|
||||
@@ -71,16 +71,16 @@ xlrd==2.0.1
|
||||
validators==0.34.0
|
||||
psutil
|
||||
sentencepiece
|
||||
soundfile==0.12.1
|
||||
soundfile==0.13.1
|
||||
|
||||
opencv-python-headless==4.10.0.84
|
||||
opencv-python-headless==4.11.0.86
|
||||
rapidocr-onnxruntime==1.3.24
|
||||
rank-bm25==0.2.2
|
||||
|
||||
faster-whisper==1.0.3
|
||||
|
||||
PyJWT[crypto]==2.10.1
|
||||
authlib==1.3.2
|
||||
authlib==1.4.1
|
||||
|
||||
black==24.8.0
|
||||
langfuse==2.44.0
|
||||
@@ -89,7 +89,7 @@ pytube==15.0.0
|
||||
|
||||
extract_msg
|
||||
pydub
|
||||
duckduckgo-search~=7.2.1
|
||||
duckduckgo-search~=7.3.0
|
||||
|
||||
## Google Drive
|
||||
google-api-python-client
|
||||
|
||||
Generated
+18
-6
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.9",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-python": "^6.1.6",
|
||||
@@ -56,7 +56,7 @@
|
||||
"prosemirror-schema-list": "^1.4.1",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-view": "^1.34.3",
|
||||
"pyodide": "^0.26.1",
|
||||
"pyodide": "^0.27.2",
|
||||
"socket.io-client": "^4.2.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
@@ -69,6 +69,7 @@
|
||||
"@sveltejs/adapter-static": "^3.0.2",
|
||||
"@sveltejs/kit": "^2.5.20",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
@@ -2343,6 +2344,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/container-queries": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
|
||||
"integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
|
||||
@@ -9366,9 +9377,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pyodide": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.1.tgz",
|
||||
"integrity": "sha512-P+Gm88nwZqY7uBgjbQH8CqqU6Ei/rDn7pS1t02sNZsbyLJMyE2OVXjgNuqVT3KqYWnyGREUN0DbBUCJqk8R0ew==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.27.2.tgz",
|
||||
"integrity": "sha512-sfA2kiUuQVRpWI4BYnU3sX5PaTTt/xrcVEmRzRcId8DzZXGGtPgCBC0gCqjUTUYSa8ofPaSjXmzESc86yvvCHg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||
@@ -25,6 +25,7 @@
|
||||
"@sveltejs/adapter-static": "^3.0.2",
|
||||
"@sveltejs/kit": "^2.5.20",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
@@ -98,7 +99,7 @@
|
||||
"prosemirror-schema-list": "^1.4.1",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-view": "^1.34.3",
|
||||
"pyodide": "^0.26.1",
|
||||
"pyodide": "^0.27.2",
|
||||
"socket.io-client": "^4.2.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"svelte-sonner": "^0.3.19",
|
||||
|
||||
+8
-8
@@ -6,7 +6,7 @@ authors = [
|
||||
]
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"fastapi==0.111.0",
|
||||
"fastapi==0.115.7",
|
||||
"uvicorn[standard]==0.30.6",
|
||||
"pydantic==2.9.2",
|
||||
"python-multipart==0.0.18",
|
||||
@@ -19,7 +19,7 @@ dependencies = [
|
||||
"passlib[bcrypt]==1.7.4",
|
||||
|
||||
"requests==2.32.3",
|
||||
"aiohttp==3.11.8",
|
||||
"aiohttp==3.11.11",
|
||||
"async-timeout",
|
||||
"aiocache",
|
||||
"aiofiles",
|
||||
@@ -62,10 +62,10 @@ dependencies = [
|
||||
"ftfy==6.2.3",
|
||||
"pypdf==4.3.1",
|
||||
"fpdf2==2.8.2",
|
||||
"pymdown-extensions==10.11.2",
|
||||
"pymdown-extensions==10.14.2",
|
||||
"docx2txt==0.8",
|
||||
"python-pptx==1.0.0",
|
||||
"unstructured==0.15.9",
|
||||
"unstructured==0.16.11",
|
||||
"nltk==3.9.1",
|
||||
"Markdown==3.7",
|
||||
"pypandoc==1.13",
|
||||
@@ -76,16 +76,16 @@ dependencies = [
|
||||
"validators==0.34.0",
|
||||
"psutil",
|
||||
"sentencepiece",
|
||||
"soundfile==0.12.1",
|
||||
"soundfile==0.13.1",
|
||||
|
||||
"opencv-python-headless==4.10.0.84",
|
||||
"opencv-python-headless==4.11.0.86",
|
||||
"rapidocr-onnxruntime==1.3.24",
|
||||
"rank-bm25==0.2.2",
|
||||
|
||||
"faster-whisper==1.0.3",
|
||||
|
||||
"PyJWT[crypto]==2.10.1",
|
||||
"authlib==1.3.2",
|
||||
"authlib==1.4.1",
|
||||
|
||||
"black==24.8.0",
|
||||
"langfuse==2.44.0",
|
||||
@@ -94,7 +94,7 @@ dependencies = [
|
||||
|
||||
"extract_msg",
|
||||
"pydub",
|
||||
"duckduckgo-search~=7.2.1",
|
||||
"duckduckgo-search~=7.3.0",
|
||||
|
||||
"google-api-python-client",
|
||||
"google-auth-httplib2",
|
||||
|
||||
@@ -9,7 +9,10 @@ const packages = [
|
||||
'scikit-learn',
|
||||
'scipy',
|
||||
'regex',
|
||||
'seaborn'
|
||||
'sympy',
|
||||
'tiktoken',
|
||||
'seaborn',
|
||||
'pytz'
|
||||
];
|
||||
|
||||
import { loadPyodide } from 'pyodide';
|
||||
|
||||
@@ -580,7 +580,7 @@ export const toggleChatPinnedStatusById = async (token: string, id: string) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const cloneChatById = async (token: string, id: string) => {
|
||||
export const cloneChatById = async (token: string, id: string, title?: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/${id}/clone`, {
|
||||
@@ -589,7 +589,10 @@ export const cloneChatById = async (token: string, id: string) => {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { authorization: `Bearer ${token}` })
|
||||
}
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...(title && { title: title })
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
|
||||
@@ -880,13 +880,14 @@ export const getChangelog = async () => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getVersionUpdates = async () => {
|
||||
export const getVersionUpdates = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_BASE_URL}/api/version/updates`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -219,7 +219,7 @@ export const deleteModelById = async (token: string, id: string) => {
|
||||
return json;
|
||||
})
|
||||
.catch((err) => {
|
||||
error = err;
|
||||
error = err.detail;
|
||||
|
||||
console.log(err);
|
||||
return null;
|
||||
|
||||
@@ -360,12 +360,7 @@ export const generateChatCompletion = async (token: string = '', body: object) =
|
||||
return [res, controller];
|
||||
};
|
||||
|
||||
export const createModel = async (
|
||||
token: string,
|
||||
tagName: string,
|
||||
content: string,
|
||||
urlIdx: string | null = null
|
||||
) => {
|
||||
export const createModel = async (token: string, payload: object, urlIdx: string | null = null) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(
|
||||
@@ -377,10 +372,7 @@ export const createModel = async (
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: tagName,
|
||||
modelfile: content
|
||||
})
|
||||
body: JSON.stringify(payload)
|
||||
}
|
||||
).catch((err) => {
|
||||
error = err;
|
||||
|
||||
@@ -322,7 +322,7 @@ export const generateOpenAIChatCompletion = async (
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
error = `${err?.detail ?? 'Network Problem'}`;
|
||||
error = `${err?.detail ?? err}`;
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
models = [];
|
||||
} else {
|
||||
const res = await _getModels(localStorage.token).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -74,7 +74,7 @@
|
||||
}, 100);
|
||||
} else {
|
||||
const res = await _getVoices(localStorage.token).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
ENABLE_TAGS_GENERATION: true,
|
||||
ENABLE_SEARCH_QUERY_GENERATION: true,
|
||||
ENABLE_RETRIEVAL_QUERY_GENERATION: true,
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: ''
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: '',
|
||||
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: ''
|
||||
};
|
||||
|
||||
let promptSuggestions = [];
|
||||
@@ -251,6 +252,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tools Function Calling Prompt')}</div>
|
||||
|
||||
<Tooltip
|
||||
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
placement="top-start"
|
||||
>
|
||||
<Textarea
|
||||
bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
|
||||
placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div class=" space-y-3 {banners.length > 0 ? ' mb-3' : ''}">
|
||||
|
||||
@@ -44,8 +44,9 @@
|
||||
let modelTag = '';
|
||||
|
||||
let createModelLoading = false;
|
||||
let createModelTag = '';
|
||||
let createModelContent = '';
|
||||
let createModelName = '';
|
||||
let createModelObject = '';
|
||||
|
||||
let createModelDigest = '';
|
||||
let createModelPullProgress = null;
|
||||
|
||||
@@ -427,10 +428,23 @@
|
||||
|
||||
const createModelHandler = async () => {
|
||||
createModelLoading = true;
|
||||
|
||||
let modelObject = {};
|
||||
// parse createModelObject
|
||||
try {
|
||||
modelObject = JSON.parse(createModelObject);
|
||||
} catch (error) {
|
||||
toast.error(`${error}`);
|
||||
createModelLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await createModel(
|
||||
localStorage.token,
|
||||
createModelTag,
|
||||
createModelContent,
|
||||
{
|
||||
model: createModelName,
|
||||
...modelObject
|
||||
},
|
||||
urlIdx
|
||||
).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
@@ -496,18 +510,22 @@
|
||||
|
||||
createModelLoading = false;
|
||||
|
||||
createModelTag = '';
|
||||
createModelContent = '';
|
||||
createModelName = '';
|
||||
createModelObject = '';
|
||||
createModelDigest = '';
|
||||
createModelPullProgress = null;
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
loading = true;
|
||||
ollamaModels = await getOllamaModels(localStorage.token, urlIdx);
|
||||
ollamaModels = await getOllamaModels(localStorage.token, urlIdx).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
console.log(ollamaModels);
|
||||
loading = false;
|
||||
if (ollamaModels) {
|
||||
loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
$: if (urlIdx !== null) {
|
||||
@@ -747,15 +765,15 @@
|
||||
placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
|
||||
modelTag: 'my-modelfile'
|
||||
})}
|
||||
bind:value={createModelTag}
|
||||
bind:value={createModelName}
|
||||
disabled={createModelLoading}
|
||||
/>
|
||||
|
||||
<textarea
|
||||
bind:value={createModelContent}
|
||||
bind:value={createModelObject}
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
|
||||
rows="6"
|
||||
placeholder={`TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`}
|
||||
placeholder={`e.g. {"model": "my-modelfile", "from": "ollama:7b"})`}
|
||||
disabled={createModelLoading}
|
||||
/>
|
||||
</div>
|
||||
@@ -1013,6 +1031,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if ollamaModels === null}
|
||||
<div class="flex justify-center items-center w-full h-full text-xs py-3">
|
||||
{$i18n.t('Failed to fetch models')}
|
||||
</div>
|
||||
{:else}
|
||||
<Spinner />
|
||||
<div class="flex justify-center items-center w-full h-full py-3">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
'duckduckgo',
|
||||
'tavily',
|
||||
'jina',
|
||||
'bing'
|
||||
'bing',
|
||||
'exa'
|
||||
];
|
||||
|
||||
let youtubeLanguage = 'en';
|
||||
@@ -261,6 +262,17 @@
|
||||
bind:value={webConfig.search.jina_api_key}
|
||||
/>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'exa'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
{$i18n.t('Exa API Key')}
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Exa API Key')}
|
||||
bind:value={webConfig.search.exa_api_key}
|
||||
/>
|
||||
</div>
|
||||
{:else if webConfig.search.engine === 'bing'}
|
||||
<div>
|
||||
<div class=" self-center text-xs font-medium mb-1">
|
||||
|
||||
@@ -61,7 +61,8 @@
|
||||
},
|
||||
features: {
|
||||
web_search: true,
|
||||
image_generation: true
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
},
|
||||
features: {
|
||||
web_search: true,
|
||||
image_generation: true
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
}
|
||||
};
|
||||
export let userIds = [];
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
},
|
||||
features: {
|
||||
web_search: true,
|
||||
image_generation: true
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -257,5 +258,13 @@
|
||||
|
||||
<Switch bind:state={permissions.features.image_generation} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Code Interpreter')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={permissions.features.code_interpreter} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
@@ -364,7 +366,7 @@
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
|
||||
{dayjs(user.created_at * 1000).format('LL')}
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
import { updateUserById } from '$lib/apis/users';
|
||||
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let show = false;
|
||||
export let selectedUser;
|
||||
@@ -87,7 +89,7 @@
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('Created at')}
|
||||
{dayjs(selectedUser.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
|
||||
{dayjs(selectedUser.created_at * 1000).format('LL')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
|
||||
|
||||
@@ -130,7 +132,7 @@
|
||||
|
||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem] justify-end">
|
||||
<div class="my-auto shrink-0">
|
||||
{dayjs(chat.updated_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
||||
{dayjs(chat.updated_at * 1000).format('LLL')}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
};
|
||||
@@ -398,7 +398,7 @@
|
||||
dir={$settings?.chatDirection ?? 'LTR'}
|
||||
>
|
||||
{#if files.length > 0}
|
||||
<div class="mx-1 mt-2.5 mb-1 flex flex-wrap gap-2">
|
||||
<div class="mx-2 mt-2.5 -mb-1 flex flex-wrap gap-2">
|
||||
{#each files as file, fileIdx}
|
||||
{#if file.type === 'image'}
|
||||
<div class=" relative group">
|
||||
@@ -411,7 +411,7 @@
|
||||
</div>
|
||||
<div class=" absolute -top-1 -right-1">
|
||||
<button
|
||||
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
|
||||
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
files.splice(fileIdx, 1);
|
||||
@@ -453,35 +453,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" flex">
|
||||
<div class="ml-1 self-end mb-1.5 flex space-x-1">
|
||||
<InputMenu
|
||||
{screenCaptureHandler}
|
||||
uploadFilesHandler={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
|
||||
type="button"
|
||||
aria-label="More"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</InputMenu>
|
||||
</div>
|
||||
|
||||
<div class="px-2.5">
|
||||
<div
|
||||
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
|
||||
class="scrollbar-hidden font-primary text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
|
||||
>
|
||||
<RichTextInput
|
||||
bind:value={content}
|
||||
@@ -528,8 +502,36 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="self-end mb-1.5 flex space-x-1 mr-1">
|
||||
<div class=" flex justify-between mb-2.5 mt-1.5 mx-0.5">
|
||||
<div class="ml-1 self-end flex space-x-1">
|
||||
<InputMenu
|
||||
{screenCaptureHandler}
|
||||
uploadFilesHandler={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
|
||||
type="button"
|
||||
aria-label="More"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</InputMenu>
|
||||
</div>
|
||||
|
||||
<div class="self-end flex space-x-1 mr-1">
|
||||
{#if content === ''}
|
||||
<Tooltip content={$i18n.t('Record voice')}>
|
||||
<button
|
||||
@@ -591,7 +593,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-6"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import isToday from 'dayjs/plugin/isToday';
|
||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isToday);
|
||||
dayjs.extend(isYesterday);
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
import { getContext, onMount } from 'svelte';
|
||||
const i18n = getContext<Writable<i18nType>>('i18n');
|
||||
@@ -153,9 +155,7 @@
|
||||
<div
|
||||
class="mt-1.5 flex flex-shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
|
||||
>
|
||||
<Tooltip
|
||||
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
|
||||
>
|
||||
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
|
||||
{dayjs(message.created_at / 1000000).format('HH:mm')}
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -174,9 +174,7 @@
|
||||
<div
|
||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||
>
|
||||
<Tooltip
|
||||
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
|
||||
>
|
||||
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
|
||||
<span class="line-clamp-1">{formatDate(message.created_at / 1000000)}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -41,11 +41,13 @@
|
||||
convertMessagesToHistory,
|
||||
copyToClipboard,
|
||||
getMessageContentParts,
|
||||
createMessagesList,
|
||||
extractSentencesForAudio,
|
||||
promptTemplate,
|
||||
splitStream,
|
||||
sleep,
|
||||
removeDetailsWithReasoning
|
||||
removeDetails,
|
||||
getPromptVariables
|
||||
} from '$lib/utils';
|
||||
|
||||
import { generateChatCompletion } from '$lib/apis/ollama';
|
||||
@@ -82,10 +84,12 @@
|
||||
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||
import Placeholder from './Placeholder.svelte';
|
||||
import NotificationToast from '../NotificationToast.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
|
||||
export let chatIdProp = '';
|
||||
|
||||
let loaded = false;
|
||||
let loading = false;
|
||||
|
||||
const eventTarget = new EventTarget();
|
||||
let controlPane;
|
||||
let controlPaneComponent;
|
||||
@@ -114,7 +118,7 @@
|
||||
let selectedToolIds = [];
|
||||
let imageGenerationEnabled = false;
|
||||
let webSearchEnabled = false;
|
||||
|
||||
let codeInterpreterEnabled = false;
|
||||
let chat = null;
|
||||
let tags = [];
|
||||
|
||||
@@ -133,6 +137,7 @@
|
||||
|
||||
$: if (chatIdProp) {
|
||||
(async () => {
|
||||
loading = true;
|
||||
console.log(chatIdProp);
|
||||
|
||||
prompt = '';
|
||||
@@ -141,11 +146,9 @@
|
||||
webSearchEnabled = false;
|
||||
imageGenerationEnabled = false;
|
||||
|
||||
loaded = false;
|
||||
|
||||
if (chatIdProp && (await loadChat())) {
|
||||
await tick();
|
||||
loaded = true;
|
||||
loading = false;
|
||||
|
||||
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
||||
try {
|
||||
@@ -223,7 +226,7 @@
|
||||
}
|
||||
|
||||
await tick();
|
||||
saveChatHandler(_chatId);
|
||||
saveChatHandler(_chatId, history);
|
||||
};
|
||||
|
||||
const chatEventHandler = async (event, cb) => {
|
||||
@@ -627,7 +630,7 @@
|
||||
} catch (e) {
|
||||
// Remove the failed doc from the files array
|
||||
files = files.filter((f) => f.name !== url);
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -715,6 +718,7 @@
|
||||
if ($page.url.searchParams.get('web-search') === 'true') {
|
||||
webSearchEnabled = true;
|
||||
}
|
||||
|
||||
if ($page.url.searchParams.get('image-generation') === 'true') {
|
||||
imageGenerationEnabled = true;
|
||||
}
|
||||
@@ -823,20 +827,6 @@
|
||||
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const createMessagesList = (responseMessageId) => {
|
||||
if (responseMessageId === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const message = history.messages[responseMessageId];
|
||||
if (message?.parentId) {
|
||||
return [...createMessagesList(message.parentId), message];
|
||||
} else {
|
||||
return [message];
|
||||
}
|
||||
};
|
||||
|
||||
const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => {
|
||||
const res = await chatCompleted(localStorage.token, {
|
||||
model: modelId,
|
||||
@@ -893,7 +883,7 @@
|
||||
};
|
||||
|
||||
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
|
||||
const messages = createMessagesList(responseMessageId);
|
||||
const messages = createMessagesList(history, responseMessageId);
|
||||
|
||||
const res = await chatAction(localStorage.token, actionId, {
|
||||
model: modelId,
|
||||
@@ -962,7 +952,7 @@
|
||||
const modelId = selectedModels[0];
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
|
||||
const messages = createMessagesList(history.currentId);
|
||||
const messages = createMessagesList(history, history.currentId);
|
||||
const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
|
||||
|
||||
const userMessageId = uuidv4();
|
||||
@@ -1007,9 +997,9 @@
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
await initChatHandler();
|
||||
await initChatHandler(history);
|
||||
} else {
|
||||
await saveChatHandler($chatId);
|
||||
await saveChatHandler($chatId, history);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1071,9 +1061,9 @@
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
await initChatHandler();
|
||||
await initChatHandler(history);
|
||||
} else {
|
||||
await saveChatHandler($chatId);
|
||||
await saveChatHandler($chatId, history);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1207,7 +1197,12 @@
|
||||
);
|
||||
|
||||
history.messages[message.id] = message;
|
||||
await chatCompletedHandler(chatId, message.model, message.id, createMessagesList(message.id));
|
||||
await chatCompletedHandler(
|
||||
chatId,
|
||||
message.model,
|
||||
message.id,
|
||||
createMessagesList(history, message.id)
|
||||
);
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
@@ -1223,7 +1218,7 @@
|
||||
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
|
||||
console.log('submitPrompt', userPrompt, $chatId);
|
||||
|
||||
const messages = createMessagesList(history.currentId);
|
||||
const messages = createMessagesList(history, history.currentId);
|
||||
const _selectedModels = selectedModels.map((modelId) =>
|
||||
$models.map((m) => m.id).includes(modelId) ? modelId : ''
|
||||
);
|
||||
@@ -1271,7 +1266,6 @@
|
||||
}
|
||||
|
||||
prompt = '';
|
||||
await tick();
|
||||
|
||||
// Reset chat input textarea
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
@@ -1313,34 +1307,25 @@
|
||||
history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
|
||||
}
|
||||
|
||||
// Wait until history/message have been updated
|
||||
await tick();
|
||||
|
||||
// focus on chat input
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
chatInput?.focus();
|
||||
|
||||
saveSessionSelectedModels();
|
||||
|
||||
await sendPrompt(userPrompt, userMessageId, { newChat: true });
|
||||
await sendPrompt(history, userPrompt, userMessageId, { newChat: true });
|
||||
};
|
||||
|
||||
const sendPrompt = async (
|
||||
_history,
|
||||
prompt: string,
|
||||
parentId: string,
|
||||
{ modelId = null, modelIdx = null, newChat = false } = {}
|
||||
) => {
|
||||
// Create new chat if newChat is true and first user message
|
||||
if (
|
||||
newChat &&
|
||||
history.messages[history.currentId].parentId === null &&
|
||||
history.messages[history.currentId].role === 'user'
|
||||
) {
|
||||
await initChatHandler();
|
||||
} else {
|
||||
await saveChatHandler($chatId);
|
||||
}
|
||||
let _chatId = JSON.parse(JSON.stringify($chatId));
|
||||
_history = JSON.parse(JSON.stringify(_history));
|
||||
|
||||
const responseMessageIds: Record<PropertyKey, string> = {};
|
||||
// If modelId is provided, use it, else use selected model
|
||||
let selectedModelIds = modelId
|
||||
? [modelId]
|
||||
@@ -1349,7 +1334,6 @@
|
||||
: selectedModels;
|
||||
|
||||
// Create response messages for each selected model
|
||||
const responseMessageIds: Record<PropertyKey, string> = {};
|
||||
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
|
||||
@@ -1384,19 +1368,26 @@
|
||||
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
|
||||
}
|
||||
}
|
||||
history = history;
|
||||
|
||||
// Create new chat if newChat is true and first user message
|
||||
if (newChat && _history.messages[_history.currentId].parentId === null) {
|
||||
_chatId = await initChatHandler(_history);
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
_history = JSON.parse(JSON.stringify(history));
|
||||
// Save chat after all messages have been created
|
||||
await saveChatHandler($chatId);
|
||||
await saveChatHandler(_chatId, _history);
|
||||
|
||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||
await Promise.all(
|
||||
selectedModelIds.map(async (modelId, _modelIdx) => {
|
||||
console.log('modelId', modelId);
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
|
||||
if (model) {
|
||||
const messages = createMessagesList(parentId);
|
||||
const messages = createMessagesList(_history, parentId);
|
||||
// If there are image files, check if model is vision capable
|
||||
const hasImages = messages.some((message) =>
|
||||
message.files?.some((file) => file.type === 'image')
|
||||
@@ -1412,7 +1403,7 @@
|
||||
|
||||
let responseMessageId =
|
||||
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
|
||||
let responseMessage = history.messages[responseMessageId];
|
||||
let responseMessage = _history.messages[responseMessageId];
|
||||
|
||||
let userContext = null;
|
||||
if ($settings?.memory ?? false) {
|
||||
@@ -1441,7 +1432,7 @@
|
||||
const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
|
||||
|
||||
scrollToBottom();
|
||||
await sendPromptSocket(model, responseMessageId, _chatId);
|
||||
await sendPromptSocket(_history, model, responseMessageId, _chatId);
|
||||
|
||||
if (chatEventEmitter) clearInterval(chatEventEmitter);
|
||||
} else {
|
||||
@@ -1454,9 +1445,9 @@
|
||||
chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
};
|
||||
|
||||
const sendPromptSocket = async (model, responseMessageId, _chatId) => {
|
||||
const responseMessage = history.messages[responseMessageId];
|
||||
const userMessage = history.messages[responseMessage.parentId];
|
||||
const sendPromptSocket = async (_history, model, responseMessageId, _chatId) => {
|
||||
const responseMessage = _history.messages[responseMessageId];
|
||||
const userMessage = _history.messages[responseMessage.parentId];
|
||||
|
||||
let files = JSON.parse(JSON.stringify(chatFiles));
|
||||
files.push(
|
||||
@@ -1504,9 +1495,9 @@
|
||||
}`
|
||||
}
|
||||
: undefined,
|
||||
...createMessagesList(responseMessageId).map((message) => ({
|
||||
...createMessagesList(_history, responseMessageId).map((message) => ({
|
||||
...message,
|
||||
content: removeDetailsWithReasoning(message.content)
|
||||
content: removeDetails(message.content, ['reasoning', 'code_interpreter'])
|
||||
}))
|
||||
]
|
||||
.filter((message) => message?.content?.trim())
|
||||
@@ -1557,9 +1548,28 @@
|
||||
|
||||
files: (files?.length ?? 0) > 0 ? files : undefined,
|
||||
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
|
||||
|
||||
features: {
|
||||
image_generation: imageGenerationEnabled,
|
||||
web_search: webSearchEnabled
|
||||
image_generation:
|
||||
$config?.features?.enable_image_generation &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.image_generation)
|
||||
? imageGenerationEnabled
|
||||
: false,
|
||||
code_interpreter:
|
||||
$user.role === 'admin' || $user?.permissions?.features?.code_interpreter
|
||||
? codeInterpreterEnabled
|
||||
: false,
|
||||
web_search:
|
||||
$config?.features?.enable_web_search &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.web_search)
|
||||
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
|
||||
: false
|
||||
},
|
||||
variables: {
|
||||
...getPromptVariables(
|
||||
$user.name,
|
||||
$settings?.userLocation ? await getAndUpdateUserLocation(localStorage.token) : undefined
|
||||
)
|
||||
},
|
||||
|
||||
session_id: $socket?.id,
|
||||
@@ -1590,12 +1600,15 @@
|
||||
},
|
||||
`${WEBUI_BASE_URL}/api`
|
||||
).catch((error) => {
|
||||
console.log(error);
|
||||
toast.error(`${error}`);
|
||||
|
||||
responseMessage.error = {
|
||||
content: error
|
||||
};
|
||||
responseMessage.done = true;
|
||||
|
||||
history.messages[responseMessageId] = responseMessage;
|
||||
history.currentId = responseMessageId;
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -1693,7 +1706,7 @@
|
||||
history.currentId = userMessageId;
|
||||
|
||||
await tick();
|
||||
await sendPrompt(userPrompt, userMessageId);
|
||||
await sendPrompt(history, userPrompt, userMessageId);
|
||||
};
|
||||
|
||||
const regenerateResponse = async (message) => {
|
||||
@@ -1705,11 +1718,11 @@
|
||||
|
||||
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
|
||||
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
|
||||
await sendPrompt(userPrompt, userMessage.id);
|
||||
await sendPrompt(history, userPrompt, userMessage.id);
|
||||
} else {
|
||||
// If there are multiple models selected, use the model of the response message for regeneration
|
||||
// e.g. many model chat
|
||||
await sendPrompt(userPrompt, userMessage.id, {
|
||||
await sendPrompt(history, userPrompt, userMessage.id, {
|
||||
modelId: message.model,
|
||||
modelIdx: message.modelIdx
|
||||
});
|
||||
@@ -1731,7 +1744,7 @@
|
||||
.at(0);
|
||||
|
||||
if (model) {
|
||||
await sendPromptSocket(model, responseMessage.id, _chatId);
|
||||
await sendPromptSocket(history, model, responseMessage.id, _chatId);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1774,7 +1787,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
await saveChatHandler(_chatId);
|
||||
await saveChatHandler(_chatId, history);
|
||||
} else {
|
||||
console.error(res);
|
||||
}
|
||||
@@ -1783,42 +1796,48 @@
|
||||
}
|
||||
};
|
||||
|
||||
const initChatHandler = async () => {
|
||||
const initChatHandler = async (history) => {
|
||||
let _chatId = $chatId;
|
||||
|
||||
if (!$temporaryChatEnabled) {
|
||||
chat = await createNewChat(localStorage.token, {
|
||||
id: $chatId,
|
||||
id: _chatId,
|
||||
title: $i18n.t('New Chat'),
|
||||
models: selectedModels,
|
||||
system: $settings.system ?? undefined,
|
||||
params: params,
|
||||
history: history,
|
||||
messages: createMessagesList(history.currentId),
|
||||
messages: createMessagesList(history, history.currentId),
|
||||
tags: [],
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
await chatId.set(chat.id);
|
||||
_chatId = chat.id;
|
||||
await chatId.set(_chatId);
|
||||
|
||||
window.history.replaceState(history.state, '', `/c/${chat.id}`);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
currentChatPage.set(1);
|
||||
|
||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||
} else {
|
||||
_chatId = 'local';
|
||||
await chatId.set('local');
|
||||
}
|
||||
await tick();
|
||||
|
||||
return _chatId;
|
||||
};
|
||||
|
||||
const saveChatHandler = async (_chatId) => {
|
||||
const saveChatHandler = async (_chatId, history) => {
|
||||
if ($chatId == _chatId) {
|
||||
if (!$temporaryChatEnabled) {
|
||||
chat = await updateChatById(localStorage.token, _chatId, {
|
||||
models: selectedModels,
|
||||
history: history,
|
||||
messages: createMessagesList(history.currentId),
|
||||
messages: createMessagesList(history, history.currentId),
|
||||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
@@ -1861,7 +1880,7 @@
|
||||
: ' '} w-full max-w-full flex flex-col"
|
||||
id="chat-container"
|
||||
>
|
||||
{#if !chatIdProp || (loaded && chatIdProp)}
|
||||
{#if chatIdProp === '' || (!loading && chatIdProp)}
|
||||
{#if $settings?.backgroundImageUrl ?? null}
|
||||
<div
|
||||
class="absolute {$showSidebar
|
||||
@@ -1921,8 +1940,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col flex-auto z-10 w-full">
|
||||
{#if $settings?.landingPageMode === 'chat' || createMessagesList(history.currentId).length > 0}
|
||||
<div class="flex flex-col flex-auto z-10 w-full @container">
|
||||
{#if $settings?.landingPageMode === 'chat' || createMessagesList(history, history.currentId).length > 0}
|
||||
<div
|
||||
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10 scrollbar-hidden"
|
||||
id="messages-container"
|
||||
@@ -1962,6 +1981,7 @@
|
||||
bind:autoScroll
|
||||
bind:selectedToolIds
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:atSelectedModel
|
||||
transparentBackground={$settings?.backgroundImageUrl ?? false}
|
||||
@@ -2013,6 +2033,7 @@
|
||||
bind:autoScroll
|
||||
bind:selectedToolIds
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:atSelectedModel
|
||||
transparentBackground={$settings?.backgroundImageUrl ?? false}
|
||||
@@ -2065,5 +2086,11 @@
|
||||
{eventTarget}
|
||||
/>
|
||||
</PaneGroup>
|
||||
{:else if loading}
|
||||
<div class=" flex items-center justify-center h-full w-full">
|
||||
<div class="m-auto">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -22,23 +22,27 @@
|
||||
import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils';
|
||||
import { transcribeAudio } from '$lib/apis/audio';
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
import { getTools } from '$lib/apis/tools';
|
||||
import { generateAutoCompletion } from '$lib/apis';
|
||||
import { deleteFileById } from '$lib/apis/files';
|
||||
|
||||
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
|
||||
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import InputMenu from './MessageInput/InputMenu.svelte';
|
||||
import Headphone from '../icons/Headphone.svelte';
|
||||
import VoiceRecording from './MessageInput/VoiceRecording.svelte';
|
||||
import FileItem from '../common/FileItem.svelte';
|
||||
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
|
||||
import Commands from './MessageInput/Commands.svelte';
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
|
||||
import RichTextInput from '../common/RichTextInput.svelte';
|
||||
import { generateAutoCompletion } from '$lib/apis';
|
||||
import { error, text } from '@sveltejs/kit';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import FileItem from '../common/FileItem.svelte';
|
||||
import Image from '../common/Image.svelte';
|
||||
import { deleteFileById } from '$lib/apis/files';
|
||||
|
||||
import XMark from '../icons/XMark.svelte';
|
||||
import Headphone from '../icons/Headphone.svelte';
|
||||
import GlobeAlt from '../icons/GlobeAlt.svelte';
|
||||
import PhotoSolid from '../icons/PhotoSolid.svelte';
|
||||
import Photo from '../icons/Photo.svelte';
|
||||
import CommandLine from '../icons/CommandLine.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -65,6 +69,7 @@
|
||||
|
||||
export let imageGenerationEnabled = false;
|
||||
export let webSearchEnabled = false;
|
||||
export let codeInterpreterEnabled = false;
|
||||
|
||||
$: onChange({
|
||||
prompt,
|
||||
@@ -211,7 +216,7 @@
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
};
|
||||
@@ -385,7 +390,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full relative">
|
||||
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || imageGenerationEnabled}
|
||||
{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
|
||||
<div
|
||||
class="px-3 pb-0.5 pt-1.5 text-left w-full flex flex-col absolute bottom-0 left-0 right-0 bg-gradient-to-t from-white dark:from-gray-900 z-10"
|
||||
>
|
||||
@@ -421,23 +426,39 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if webSearchEnabled || ($settings?.webSearch ?? false) === 'always'}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
|
||||
<div class="pl-1">
|
||||
<span class="relative flex size-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"
|
||||
/>
|
||||
<span class="relative inline-flex rounded-full size-2 bg-blue-500" />
|
||||
</span>
|
||||
</div>
|
||||
<div class=" translate-y-[0.5px]">{$i18n.t('Search the internet')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if imageGenerationEnabled}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
|
||||
<div class="pl-1">
|
||||
<span class="relative flex size-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-teal-400 opacity-75"
|
||||
/>
|
||||
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
|
||||
<span class="relative inline-flex rounded-full size-2 bg-teal-500" />
|
||||
</span>
|
||||
</div>
|
||||
<div class=" ">{$i18n.t('Image generation')}</div>
|
||||
<div class=" translate-y-[0.5px]">{$i18n.t('Generate an image')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if webSearchEnabled}
|
||||
{#if codeInterpreterEnabled}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
|
||||
<div class="pl-1">
|
||||
@@ -448,7 +469,7 @@
|
||||
<span class="relative inline-flex rounded-full size-2 bg-green-500" />
|
||||
</span>
|
||||
</div>
|
||||
<div class=" ">{$i18n.t('Search the web')}</div>
|
||||
<div class=" translate-y-[0.5px]">{$i18n.t('Execute code for analysis')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -568,7 +589,7 @@
|
||||
dir={$settings?.chatDirection ?? 'LTR'}
|
||||
>
|
||||
{#if files.length > 0}
|
||||
<div class="mx-1 mt-2.5 mb-1 flex items-center flex-wrap gap-2">
|
||||
<div class="mx-2 mt-2.5 -mb-1 flex items-center flex-wrap gap-2">
|
||||
{#each files as file, fileIdx}
|
||||
{#if file.type === 'image'}
|
||||
<div class=" relative group">
|
||||
@@ -606,7 +627,7 @@
|
||||
</div>
|
||||
<div class=" absolute -top-1 -right-1">
|
||||
<button
|
||||
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
|
||||
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
files.splice(fileIdx, 1);
|
||||
@@ -656,65 +677,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" flex">
|
||||
<div class="ml-1 self-end mb-1.5 flex space-x-1">
|
||||
<InputMenu
|
||||
bind:imageGenerationEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:selectedToolIds
|
||||
{screenCaptureHandler}
|
||||
uploadFilesHandler={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
uploadGoogleDriveHandler={async () => {
|
||||
try {
|
||||
const fileData = await createPicker();
|
||||
if (fileData) {
|
||||
const file = new File([fileData.blob], fileData.name, {
|
||||
type: fileData.blob.type
|
||||
});
|
||||
await uploadFileHandler(file);
|
||||
} else {
|
||||
console.log('No file was selected from Google Drive');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Google Drive Error:', error);
|
||||
toast.error(
|
||||
$i18n.t('Error accessing Google Drive: {{error}}', {
|
||||
error: error.message
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onClose={async () => {
|
||||
await tick();
|
||||
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
chatInput?.focus();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
|
||||
type="button"
|
||||
aria-label="More"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</InputMenu>
|
||||
</div>
|
||||
|
||||
<div class="px-2.5">
|
||||
{#if $settings?.richTextInput ?? true}
|
||||
<div
|
||||
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
|
||||
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
|
||||
>
|
||||
<RichTextInput
|
||||
bind:this={chatInputElement}
|
||||
@@ -918,7 +884,7 @@
|
||||
<textarea
|
||||
id="chat-input"
|
||||
bind:this={chatInputElement}
|
||||
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px]"
|
||||
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none"
|
||||
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
|
||||
bind:value={prompt}
|
||||
on:keypress={(e) => {
|
||||
@@ -1107,8 +1073,125 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="self-end mb-1.5 flex space-x-1 mr-1">
|
||||
<div class=" flex justify-between mt-1.5 mb-2.5 mx-0.5 max-w-full">
|
||||
<div class="ml-1 self-end gap-0.5 flex items-center flex-1 max-w-[80%]">
|
||||
<InputMenu
|
||||
bind:selectedToolIds
|
||||
{screenCaptureHandler}
|
||||
{inputFilesHandler}
|
||||
uploadFilesHandler={() => {
|
||||
filesInputElement.click();
|
||||
}}
|
||||
uploadGoogleDriveHandler={async () => {
|
||||
try {
|
||||
const fileData = await createPicker();
|
||||
if (fileData) {
|
||||
const file = new File([fileData.blob], fileData.name, {
|
||||
type: fileData.blob.type
|
||||
});
|
||||
await uploadFileHandler(file);
|
||||
} else {
|
||||
console.log('No file was selected from Google Drive');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Google Drive Error:', error);
|
||||
toast.error(
|
||||
$i18n.t('Error accessing Google Drive: {{error}}', {
|
||||
error: error.message
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onClose={async () => {
|
||||
await tick();
|
||||
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
chatInput?.focus();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="bg-transparent hover:bg-gray-100 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
|
||||
type="button"
|
||||
aria-label="More"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</InputMenu>
|
||||
|
||||
<div class="flex gap-0.5 items-center overflow-x-auto scrollbar-none flex-1">
|
||||
{#if $_user}
|
||||
{#if $config?.features?.enable_web_search && ($_user.role === 'admin' || $_user?.permissions?.features?.web_search)}
|
||||
<Tooltip content={$i18n.t('Search the internet')} placement="top">
|
||||
<button
|
||||
on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
|
||||
type="button"
|
||||
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {webSearchEnabled ||
|
||||
($settings?.webSearch ?? false) === 'always'
|
||||
? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400'
|
||||
: 'bg-transparent text-gray-600 dark:text-gray-400 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}"
|
||||
>
|
||||
<GlobeAlt className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
>{$i18n.t('Web Search')}</span
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_image_generation && ($_user.role === 'admin' || $_user?.permissions?.features?.image_generation)}
|
||||
<Tooltip content={$i18n.t('Generate an image')} placement="top">
|
||||
<button
|
||||
on:click|preventDefault={() =>
|
||||
(imageGenerationEnabled = !imageGenerationEnabled)}
|
||||
type="button"
|
||||
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {imageGenerationEnabled
|
||||
? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
|
||||
: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
|
||||
>
|
||||
<Photo className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
>{$i18n.t('Image')}</span
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if $_user.role === 'admin' || $_user?.permissions?.features?.code_interpreter}
|
||||
<Tooltip content={$i18n.t('Execute code for analysis')} placement="top">
|
||||
<button
|
||||
on:click|preventDefault={() =>
|
||||
(codeInterpreterEnabled = !codeInterpreterEnabled)}
|
||||
type="button"
|
||||
class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {codeInterpreterEnabled
|
||||
? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
|
||||
: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
|
||||
>
|
||||
<CommandLine className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @sm:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
>{$i18n.t('Code Interpreter')}</span
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="self-end flex space-x-1 mr-1 flex-shrink-0">
|
||||
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|
||||
<Tooltip content={$i18n.t('Record voice')}>
|
||||
<button
|
||||
@@ -1163,7 +1246,10 @@
|
||||
<div class=" flex items-center">
|
||||
<Tooltip content={$i18n.t('Call')}>
|
||||
<button
|
||||
class=" bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full p-2 self-center"
|
||||
class=" {webSearchEnabled ||
|
||||
($settings?.webSearch ?? false) === 'always'
|
||||
? 'bg-blue-500 text-white hover:bg-blue-400 '
|
||||
: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100'} transition rounded-full p-1.5 self-center"
|
||||
type="button"
|
||||
on:click={async () => {
|
||||
if (selectedModels.length > 1) {
|
||||
@@ -1216,7 +1302,9 @@
|
||||
<button
|
||||
id="send-message-button"
|
||||
class="{prompt !== ''
|
||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
|
||||
? 'bg-blue-500 text-white hover:bg-blue-400 '
|
||||
: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 self-center"
|
||||
type="submit"
|
||||
disabled={prompt === ''}
|
||||
@@ -1225,7 +1313,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-6"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -1250,7 +1338,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="size-6"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
|
||||
@@ -15,39 +15,30 @@
|
||||
import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
|
||||
import CameraSolid from '$lib/components/icons/CameraSolid.svelte';
|
||||
import PhotoSolid from '$lib/components/icons/PhotoSolid.svelte';
|
||||
import CommandLineSolid from '$lib/components/icons/CommandLineSolid.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let screenCaptureHandler: Function;
|
||||
export let uploadFilesHandler: Function;
|
||||
export let inputFilesHandler: Function;
|
||||
|
||||
export let uploadGoogleDriveHandler: Function;
|
||||
|
||||
export let selectedToolIds: string[] = [];
|
||||
|
||||
export let webSearchEnabled: boolean;
|
||||
export let imageGenerationEnabled: boolean;
|
||||
|
||||
export let onClose: Function;
|
||||
|
||||
let tools = {};
|
||||
let show = false;
|
||||
|
||||
let showImageGeneration = false;
|
||||
|
||||
$: showImageGeneration =
|
||||
$config?.features?.enable_image_generation &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.image_generation);
|
||||
|
||||
let showWebSearch = false;
|
||||
|
||||
$: showWebSearch =
|
||||
$config?.features?.enable_web_search &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.web_search);
|
||||
|
||||
$: if (show) {
|
||||
init();
|
||||
}
|
||||
|
||||
let fileUploadEnabled = true;
|
||||
$: fileUploadEnabled = $user.role === 'admin' || $user?.permissions?.chat?.file_upload;
|
||||
|
||||
const init = async () => {
|
||||
if ($_tools === null) {
|
||||
await _tools.set(await getTools(localStorage.token));
|
||||
@@ -62,8 +53,31 @@
|
||||
return a;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const detectMobile = () => {
|
||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||
return /android|iphone|ipad|ipod|windows phone/i.test(userAgent);
|
||||
};
|
||||
|
||||
function handleFileChange(event) {
|
||||
const inputFiles = Array.from(event.target?.files);
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
console.log(inputFiles);
|
||||
inputFilesHandler(inputFiles);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Hidden file input used to open the camera on mobile -->
|
||||
<input
|
||||
id="camera-input"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
capture="environment"
|
||||
on:change={handleFileChange}
|
||||
style="display: none;"
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
bind:show
|
||||
on:change={(e) => {
|
||||
@@ -129,63 +143,51 @@
|
||||
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||
{/if}
|
||||
|
||||
{#if showImageGeneration}
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
|
||||
on:click={() => {
|
||||
imageGenerationEnabled = !imageGenerationEnabled;
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
<PhotoSolid />
|
||||
<div class=" line-clamp-1">{$i18n.t('Image')}</div>
|
||||
</div>
|
||||
|
||||
<Switch state={imageGenerationEnabled} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if showWebSearch}
|
||||
<button
|
||||
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
|
||||
on:click={() => {
|
||||
webSearchEnabled = !webSearchEnabled;
|
||||
}}
|
||||
>
|
||||
<div class="flex-1 flex items-center gap-2">
|
||||
<GlobeAltSolid />
|
||||
<div class=" line-clamp-1">{$i18n.t('Web Search')}</div>
|
||||
</div>
|
||||
|
||||
<Switch state={webSearchEnabled} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if showImageGeneration || showWebSearch}
|
||||
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||
{/if}
|
||||
|
||||
{#if !$mobile}
|
||||
<Tooltip
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
screenCaptureHandler();
|
||||
if (fileUploadEnabled) {
|
||||
if (!detectMobile()) {
|
||||
screenCaptureHandler();
|
||||
} else {
|
||||
const cameraInputElement = document.getElementById('camera-input');
|
||||
|
||||
if (cameraInputElement) {
|
||||
cameraInputElement.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CameraSolid />
|
||||
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
||||
on:click={() => {
|
||||
uploadFilesHandler();
|
||||
}}
|
||||
<Tooltip
|
||||
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||
className="w-full"
|
||||
>
|
||||
<DocumentArrowUpSolid />
|
||||
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
|
||||
? 'opacity-50'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if (fileUploadEnabled) {
|
||||
uploadFilesHandler();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DocumentArrowUpSolid />
|
||||
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
||||
</DropdownMenu.Item>
|
||||
</Tooltip>
|
||||
|
||||
{#if $config?.features?.enable_google_drive_integration}
|
||||
<DropdownMenu.Item
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
history.currentId = userMessageId;
|
||||
|
||||
await tick();
|
||||
await sendPrompt(userPrompt, userMessageId);
|
||||
await sendPrompt(history, userPrompt, userMessageId);
|
||||
} else {
|
||||
// Edit user message
|
||||
history.messages[messageId].content = content;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
export let token;
|
||||
export let lang = '';
|
||||
export let code = '';
|
||||
export let attributes = {};
|
||||
|
||||
export let className = 'my-2';
|
||||
export let editorClassName = '';
|
||||
@@ -49,6 +50,7 @@
|
||||
let stdout = null;
|
||||
let stderr = null;
|
||||
let result = null;
|
||||
let files = null;
|
||||
|
||||
let copied = false;
|
||||
let saved = false;
|
||||
@@ -109,86 +111,7 @@
|
||||
};
|
||||
|
||||
const executePython = async (code) => {
|
||||
if (!code.includes('input') && !code.includes('matplotlib')) {
|
||||
executePythonAsWorker(code);
|
||||
} else {
|
||||
result = null;
|
||||
stdout = null;
|
||||
stderr = null;
|
||||
|
||||
executing = true;
|
||||
|
||||
document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
|
||||
|
||||
let pyodide = await loadPyodide({
|
||||
indexURL: '/pyodide/',
|
||||
stdout: (text) => {
|
||||
console.log('Python output:', text);
|
||||
|
||||
if (stdout) {
|
||||
stdout += `${text}\n`;
|
||||
} else {
|
||||
stdout = `${text}\n`;
|
||||
}
|
||||
},
|
||||
stderr: (text) => {
|
||||
console.log('An error occurred:', text);
|
||||
if (stderr) {
|
||||
stderr += `${text}\n`;
|
||||
} else {
|
||||
stderr = `${text}\n`;
|
||||
}
|
||||
},
|
||||
packages: ['micropip']
|
||||
});
|
||||
|
||||
try {
|
||||
const micropip = pyodide.pyimport('micropip');
|
||||
|
||||
// await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
|
||||
|
||||
let packages = [
|
||||
code.includes('requests') ? 'requests' : null,
|
||||
code.includes('bs4') ? 'beautifulsoup4' : null,
|
||||
code.includes('numpy') ? 'numpy' : null,
|
||||
code.includes('pandas') ? 'pandas' : null,
|
||||
code.includes('matplotlib') ? 'matplotlib' : null,
|
||||
code.includes('sklearn') ? 'scikit-learn' : null,
|
||||
code.includes('scipy') ? 'scipy' : null,
|
||||
code.includes('re') ? 'regex' : null,
|
||||
code.includes('seaborn') ? 'seaborn' : null
|
||||
].filter(Boolean);
|
||||
|
||||
console.log(packages);
|
||||
await micropip.install(packages);
|
||||
|
||||
result = await pyodide.runPythonAsync(`from js import prompt
|
||||
def input(p):
|
||||
return prompt(p)
|
||||
__builtins__.input = input`);
|
||||
|
||||
result = await pyodide.runPython(code);
|
||||
|
||||
if (!result) {
|
||||
result = '[NO OUTPUT]';
|
||||
}
|
||||
|
||||
console.log(result);
|
||||
console.log(stdout);
|
||||
console.log(stderr);
|
||||
|
||||
const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
|
||||
|
||||
if (pltCanvasElement?.innerHTML !== '') {
|
||||
pltCanvasElement.classList.add('pt-4');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
stderr = error;
|
||||
}
|
||||
|
||||
executing = false;
|
||||
}
|
||||
executePythonAsWorker(code);
|
||||
};
|
||||
|
||||
const executePythonAsWorker = async (code) => {
|
||||
@@ -206,7 +129,11 @@ __builtins__.input = input`);
|
||||
code.includes('sklearn') ? 'scikit-learn' : null,
|
||||
code.includes('scipy') ? 'scipy' : null,
|
||||
code.includes('re') ? 'regex' : null,
|
||||
code.includes('seaborn') ? 'seaborn' : null
|
||||
code.includes('seaborn') ? 'seaborn' : null,
|
||||
code.includes('sympy') ? 'sympy' : null,
|
||||
code.includes('tiktoken') ? 'tiktoken' : null,
|
||||
code.includes('matplotlib') ? 'matplotlib' : null,
|
||||
code.includes('pytz') ? 'pytz' : null
|
||||
].filter(Boolean);
|
||||
|
||||
console.log(packages);
|
||||
@@ -233,7 +160,31 @@ __builtins__.input = input`);
|
||||
|
||||
console.log(id, data);
|
||||
|
||||
data['stdout'] && (stdout = data['stdout']);
|
||||
if (data['stdout']) {
|
||||
stdout = data['stdout'];
|
||||
const stdoutLines = stdout.split('\n');
|
||||
|
||||
for (const [idx, line] of stdoutLines.entries()) {
|
||||
if (line.startsWith('data:image/png;base64')) {
|
||||
if (files) {
|
||||
files.push({
|
||||
type: 'image/png',
|
||||
data: line
|
||||
});
|
||||
} else {
|
||||
files = [
|
||||
{
|
||||
type: 'image/png',
|
||||
data: line
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
stdout = stdout.replace(`${line}\n`, ``);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data['stderr'] && (stderr = data['stderr']);
|
||||
data['result'] && (result = data['result']);
|
||||
|
||||
@@ -279,6 +230,36 @@ __builtins__.input = input`);
|
||||
|
||||
$: dispatch('code', { lang, code });
|
||||
|
||||
$: if (attributes) {
|
||||
onAttributesUpdate();
|
||||
}
|
||||
|
||||
const onAttributesUpdate = () => {
|
||||
if (attributes?.output) {
|
||||
// Create a helper function to unescape HTML entities
|
||||
const unescapeHtml = (html) => {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.innerHTML = html;
|
||||
return textArea.value;
|
||||
};
|
||||
|
||||
try {
|
||||
// Unescape the HTML-encoded string
|
||||
const unescapedOutput = unescapeHtml(attributes.output);
|
||||
|
||||
// Parse the unescaped string into JSON
|
||||
const output = JSON.parse(unescapedOutput);
|
||||
|
||||
// Assign the parsed values to variables
|
||||
stdout = output.stdout;
|
||||
stderr = output.stderr;
|
||||
result = output.result;
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
console.log('codeblock', lang, code);
|
||||
|
||||
@@ -376,18 +357,43 @@ __builtins__.input = input`);
|
||||
|
||||
<div
|
||||
id="plt-canvas-{id}"
|
||||
class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
|
||||
class="bg-gray-50 dark:bg-[#202123] dark:text-white max-w-full overflow-x-auto scrollbar-hidden"
|
||||
/>
|
||||
|
||||
{#if executing}
|
||||
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">Running...</div>
|
||||
</div>
|
||||
{:else if stdout || stderr || result}
|
||||
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">{stdout || stderr || result}</div>
|
||||
{#if executing || stdout || stderr || result}
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-[#202123] dark:text-white !rounded-b-lg py-4 px-4 flex flex-col gap-2"
|
||||
>
|
||||
{#if executing}
|
||||
<div class=" ">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">Running...</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if stdout || stderr}
|
||||
<div class=" ">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">{stdout || stderr}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if result || files}
|
||||
<div class=" ">
|
||||
<div class=" text-gray-500 text-xs mb-1">RESULT</div>
|
||||
{#if result}
|
||||
<div class="text-sm">{`${JSON.stringify(result)}`}</div>
|
||||
{/if}
|
||||
{#if files}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each files as file}
|
||||
{#if file.type.startsWith('image')}
|
||||
<img src={file.data} alt="Output" />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export let id: string;
|
||||
export let tokens: Token[];
|
||||
export let top = true;
|
||||
export let attributes = {};
|
||||
|
||||
export let save = false;
|
||||
export let onSourceClick: Function = () => {};
|
||||
@@ -83,6 +84,7 @@
|
||||
{token}
|
||||
lang={token?.lang ?? ''}
|
||||
code={token?.text ?? ''}
|
||||
{attributes}
|
||||
{save}
|
||||
on:code={(e) => {
|
||||
dispatch('code', e.detail);
|
||||
@@ -195,9 +197,13 @@
|
||||
</ul>
|
||||
{/if}
|
||||
{:else if token.type === 'details'}
|
||||
<Collapsible title={token.summary} attributes={token?.attributes} className="w-fit space-y-1">
|
||||
<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
|
||||
<div class=" mb-1.5" slot="content">
|
||||
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
|
||||
<svelte:self
|
||||
id={`${id}-${tokenIdx}-d`}
|
||||
tokens={marked.lexer(token.text)}
|
||||
attributes={token?.attributes}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible>
|
||||
{:else if token.type === 'html'}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
{rateMessage}
|
||||
{actionMessage}
|
||||
{submitMessage}
|
||||
{deleteMessage}
|
||||
{continueResponse}
|
||||
{regenerateResponse}
|
||||
{addMessages}
|
||||
@@ -95,6 +96,7 @@
|
||||
{rateMessage}
|
||||
{actionMessage}
|
||||
{submitMessage}
|
||||
{deleteMessage}
|
||||
{continueResponse}
|
||||
{regenerateResponse}
|
||||
{mergeResponses}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
import Markdown from './Markdown.svelte';
|
||||
import Name from './Name.svelte';
|
||||
import Skeleton from './Skeleton.svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
const i18n = getContext('i18n');
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let chatId;
|
||||
export let history;
|
||||
@@ -32,6 +34,8 @@
|
||||
export let actionMessage: Function;
|
||||
|
||||
export let submitMessage: Function;
|
||||
export let deleteMessage: Function;
|
||||
|
||||
export let continueResponse: Function;
|
||||
export let regenerateResponse: Function;
|
||||
export let mergeResponses: Function;
|
||||
@@ -226,6 +230,7 @@
|
||||
{editMessage}
|
||||
{saveMessage}
|
||||
{rateMessage}
|
||||
{deleteMessage}
|
||||
{actionMessage}
|
||||
{submitMessage}
|
||||
{continueResponse}
|
||||
@@ -264,7 +269,7 @@
|
||||
<span
|
||||
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
|
||||
>
|
||||
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
|
||||
{dayjs(message.timestamp * 1000).format('LT')}
|
||||
</span>
|
||||
{/if}
|
||||
</Name>
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
export let saveMessage: Function;
|
||||
export let rateMessage: Function;
|
||||
export let actionMessage: Function;
|
||||
export let deleteMessage: Function;
|
||||
|
||||
export let submitMessage: Function;
|
||||
export let continueResponse: Function;
|
||||
@@ -461,6 +462,10 @@
|
||||
feedbackLoading = false;
|
||||
};
|
||||
|
||||
const deleteMessageHandler = async () => {
|
||||
deleteMessage(message.id);
|
||||
};
|
||||
|
||||
$: if (!edit) {
|
||||
(async () => {
|
||||
await tick();
|
||||
@@ -500,7 +505,7 @@
|
||||
<div
|
||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||
>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -1176,6 +1181,36 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if siblings.length > 1}
|
||||
<Tooltip content={$i18n.t('Delete')} placement="bottom">
|
||||
<button
|
||||
type="button"
|
||||
id="continue-response-button"
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
|
||||
on:click={() => {
|
||||
deleteMessageHandler();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if isLastMessage}
|
||||
{#each model?.actions ?? [] as action}
|
||||
<Tooltip content={action.name} placement="bottom">
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
import FileItem from '$lib/components/common/FileItem.svelte';
|
||||
import Markdown from './Markdown.svelte';
|
||||
import Image from '$lib/components/common/Image.svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let user;
|
||||
|
||||
@@ -112,7 +114,7 @@
|
||||
<div
|
||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||
>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
export let selectedToolIds = [];
|
||||
export let imageGenerationEnabled = false;
|
||||
export let codeInterpreterEnabled = false;
|
||||
export let webSearchEnabled = false;
|
||||
|
||||
let models = [];
|
||||
@@ -87,7 +88,7 @@
|
||||
onMount(() => {});
|
||||
</script>
|
||||
|
||||
<div class="m-auto w-full max-w-6xl px-2 xl: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">
|
||||
{#if $temporaryChatEnabled}
|
||||
<Tooltip
|
||||
content="This chat won't appear in history and your messages will not be saved."
|
||||
@@ -104,7 +105,7 @@
|
||||
class="w-full text-3xl text-gray-800 dark:text-gray-100 font-medium text-center flex items-center gap-4 font-primary"
|
||||
>
|
||||
<div class="w-full flex flex-col justify-center items-center">
|
||||
<div class="flex flex-row justify-center gap-3 sm:gap-3.5 w-fit px-5">
|
||||
<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5">
|
||||
<div class="flex flex-shrink-0 justify-center">
|
||||
<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
|
||||
{#each models as model, modelIdx}
|
||||
@@ -125,7 +126,7 @@
|
||||
($i18n.language === 'dg-DG'
|
||||
? `/doge.png`
|
||||
: `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||
class=" size-9 sm:size-10 rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||
class=" size-9 @sm:size-10 rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||
alt="logo"
|
||||
draggable="false"
|
||||
/>
|
||||
@@ -135,7 +136,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" text-3xl sm:text-4xl line-clamp-1" in:fade={{ duration: 100 }}>
|
||||
<div class=" text-3xl @sm:text-4xl line-clamp-1" in:fade={{ duration: 100 }}>
|
||||
{#if models[selectedModelIdx]?.name}
|
||||
{models[selectedModelIdx]?.name}
|
||||
{:else}
|
||||
@@ -183,11 +184,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="text-base font-normal xl:translate-x-6 md:max-w-3xl w-full py-3 {atSelectedModel
|
||||
? 'mt-2'
|
||||
: ''}"
|
||||
>
|
||||
<div class="text-base font-normal @md:max-w-3xl w-full py-3 {atSelectedModel ? 'mt-2' : ''}">
|
||||
<MessageInput
|
||||
{history}
|
||||
{selectedModels}
|
||||
@@ -196,6 +193,7 @@
|
||||
bind:autoScroll
|
||||
bind:selectedToolIds
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:atSelectedModel
|
||||
{transparentBackground}
|
||||
@@ -218,6 +216,7 @@
|
||||
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
|
||||
$config?.default_prompt_suggestions ??
|
||||
[]}
|
||||
inputValue={prompt}
|
||||
on:select={(e) => {
|
||||
selectSuggestionPrompt(e.detail);
|
||||
}}
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
export let params = {
|
||||
// Advanced
|
||||
stream_response: null, // Set stream responses for this model individually
|
||||
function_calling: null,
|
||||
seed: null,
|
||||
stop: null,
|
||||
temperature: null,
|
||||
reasoning_effort: null,
|
||||
frequency_penalty: null,
|
||||
repeat_last_n: null,
|
||||
mirostat: null,
|
||||
@@ -80,6 +82,35 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Function Calling')}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
params.function_calling = (params?.function_calling ?? null) === null ? 'native' : null;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if params.function_calling === 'native'}
|
||||
<span class="ml-2 self-center">{$i18n.t('Native')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
@@ -158,7 +189,7 @@
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
class="w-full rounded-lg py-2 px-1 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter stop sequence')}
|
||||
bind:value={params.stop}
|
||||
@@ -224,6 +255,49 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Reasoning Effort')}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.reasoning_effort = (params?.reasoning_effort ?? null) === null ? 'medium' : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.reasoning_effort ?? null) === null}
|
||||
<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{#if (params?.reasoning_effort ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-1 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
type="text"
|
||||
placeholder={$i18n.t('Enter reasoning effort')}
|
||||
bind:value={params.reasoning_effort}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}, 100);
|
||||
} else {
|
||||
const res = await _getVoices(localStorage.token).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
let params = {
|
||||
// Advanced
|
||||
stream_response: null,
|
||||
function_calling: null,
|
||||
seed: null,
|
||||
temperature: null,
|
||||
frequency_penalty: null,
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
let voiceInterruption = false;
|
||||
let hapticFeedback = false;
|
||||
|
||||
let webSearch = null;
|
||||
|
||||
const toggleSplitLargeChunks = async () => {
|
||||
splitLargeChunks = !splitLargeChunks;
|
||||
saveSettings({ splitLargeChunks: splitLargeChunks });
|
||||
@@ -198,6 +200,11 @@
|
||||
});
|
||||
};
|
||||
|
||||
const toggleWebSearch = async () => {
|
||||
webSearch = webSearch === null ? 'always' : null;
|
||||
saveSettings({ webSearch: webSearch });
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
titleAutoGenerate = $settings?.title?.auto ?? true;
|
||||
autoTags = $settings.autoTags ?? true;
|
||||
@@ -235,6 +242,7 @@
|
||||
}
|
||||
|
||||
backgroundImageUrl = $settings.backgroundImageUrl ?? null;
|
||||
webSearch = $settings.webSearch ?? null;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -666,6 +674,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Web Search in Chat')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleWebSearch();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if webSearch === 'always'}
|
||||
<span class="ml-2 self-center">{$i18n.t('Always')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import EditMemoryModal from './EditMemoryModal.svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -84,9 +86,7 @@
|
||||
</td>
|
||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
||||
<div class="my-auto whitespace-nowrap">
|
||||
{dayjs(memory.updated_at * 1000).format(
|
||||
$i18n.t('MMMM DD, YYYY hh:mm:ss A')
|
||||
)}
|
||||
{dayjs(memory.updated_at * 1000).format('LLL')}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-1">
|
||||
|
||||
@@ -118,7 +118,8 @@
|
||||
'displayemoji',
|
||||
'save',
|
||||
'interfaceoptions',
|
||||
'interfacecustomization'
|
||||
'interfacecustomization',
|
||||
'alwaysonwebsearch'
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,53 +1,131 @@
|
||||
<script lang="ts">
|
||||
import Fuse from 'fuse.js';
|
||||
import Bolt from '$lib/components/icons/Bolt.svelte';
|
||||
import { onMount, getContext, createEventDispatcher } from 'svelte';
|
||||
import { WEBUI_NAME } from '$lib/stores';
|
||||
import { WEBUI_VERSION } from '$lib/constants';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let suggestionPrompts = [];
|
||||
export let className = '';
|
||||
export let inputValue = '';
|
||||
|
||||
let prompts = [];
|
||||
let sortedPrompts = [];
|
||||
|
||||
$: prompts = (suggestionPrompts ?? [])
|
||||
.reduce((acc, current) => [...acc, ...[current]], [])
|
||||
.sort(() => Math.random() - 0.5);
|
||||
const fuseOptions = {
|
||||
keys: ['content', 'title'],
|
||||
threshold: 0.5
|
||||
};
|
||||
|
||||
let fuse;
|
||||
let filteredPrompts = [];
|
||||
|
||||
// Initialize Fuse
|
||||
$: fuse = new Fuse(sortedPrompts, fuseOptions);
|
||||
|
||||
// Update the filteredPrompts if inputValue changes
|
||||
// Only increase version if something wirklich geändert hat
|
||||
$: getFilteredPrompts(inputValue);
|
||||
|
||||
// Helper function to check if arrays are the same
|
||||
// (based on unique IDs oder content)
|
||||
function arraysEqual(a, b) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if ((a[i].id ?? a[i].content) !== (b[i].id ?? b[i].content)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const getFilteredPrompts = (inputValue) => {
|
||||
const newFilteredPrompts = inputValue.trim()
|
||||
? fuse.search(inputValue.trim()).map((result) => result.item)
|
||||
: sortedPrompts;
|
||||
|
||||
// Compare with the oldFilteredPrompts
|
||||
// If there's a difference, update array + version
|
||||
if (!arraysEqual(filteredPrompts, newFilteredPrompts)) {
|
||||
filteredPrompts = newFilteredPrompts;
|
||||
}
|
||||
};
|
||||
|
||||
$: if (suggestionPrompts) {
|
||||
sortedPrompts = [...(suggestionPrompts ?? [])].sort(() => Math.random() - 0.5);
|
||||
getFilteredPrompts(inputValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if prompts.length > 0}
|
||||
<div class="mb-1 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
|
||||
<div class="mb-1 flex gap-1 text-xs font-medium items-center text-gray-400 dark:text-gray-600">
|
||||
{#if filteredPrompts.length > 0}
|
||||
<Bolt />
|
||||
{$i18n.t('Suggested')}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<!-- Keine Vorschläge -->
|
||||
|
||||
<div class=" h-40 max-h-full overflow-auto scrollbar-none {className}">
|
||||
{#each prompts as prompt, promptIdx}
|
||||
<button
|
||||
class="flex flex-col flex-1 shrink-0 w-full justify-between px-3 py-2 rounded-xl bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition group"
|
||||
on:click={() => {
|
||||
dispatch('select', prompt.content);
|
||||
}}
|
||||
<div
|
||||
class="flex w-full text-center items-center justify-center self-start text-gray-400 dark:text-gray-600"
|
||||
>
|
||||
<div class="flex flex-col text-left">
|
||||
{#if prompt.title && prompt.title[0] !== ''}
|
||||
<div
|
||||
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||
>
|
||||
{prompt.title[0]}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">{prompt.title[1]}</div>
|
||||
{:else}
|
||||
<div
|
||||
class=" font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||
>
|
||||
{prompt.content}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">Prompt</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{$WEBUI_NAME} ‧ v{WEBUI_VERSION}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="h-40 overflow-auto scrollbar-none {className} items-start">
|
||||
{#if filteredPrompts.length > 0}
|
||||
{#each filteredPrompts as prompt, idx (prompt.id || prompt.content)}
|
||||
<button
|
||||
class="waterfall flex flex-col flex-1 shrink-0 w-full justify-between
|
||||
px-3 py-2 rounded-xl bg-transparent hover:bg-black/5
|
||||
dark:hover:bg-white/5 transition group"
|
||||
style="animation-delay: {idx * 60}ms"
|
||||
on:click={() => dispatch('select', prompt.content)}
|
||||
>
|
||||
<div class="flex flex-col text-left">
|
||||
{#if prompt.title && prompt.title[0] !== ''}
|
||||
<div
|
||||
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||
>
|
||||
{prompt.title[0]}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">
|
||||
{prompt.title[1]}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="font-medium dark:text-gray-300 dark:group-hover:text-gray-200 transition line-clamp-1"
|
||||
>
|
||||
{prompt.content}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 font-normal line-clamp-1">{$i18n.t('Prompt')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Waterfall animation for the suggestions */
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.waterfall {
|
||||
opacity: 0;
|
||||
animation-name: fadeInUp;
|
||||
animation-duration: 200ms;
|
||||
animation-fill-mode: forwards;
|
||||
animation-timing-function: ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import dayjs from '$lib/dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
async function loadLocale(locales) {
|
||||
for (const locale of locales) {
|
||||
try {
|
||||
dayjs.locale(locale);
|
||||
break; // Stop after successfully loading the first available locale
|
||||
} catch (error) {
|
||||
console.error(`Could not load locale '${locale}':`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming $i18n.languages is an array of language codes
|
||||
$: loadLocale($i18n.languages);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
$: dispatch('change', open);
|
||||
@@ -37,20 +59,36 @@
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done !==
|
||||
'true'
|
||||
class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done &&
|
||||
attributes?.done !== 'true'
|
||||
? 'shimmer'
|
||||
: ''}
|
||||
"
|
||||
>
|
||||
{#if attributes?.done !== 'true'}
|
||||
{#if attributes?.done && attributes?.done !== 'true'}
|
||||
<div>
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="">
|
||||
{title}
|
||||
{#if attributes?.type === 'reasoning'}
|
||||
{#if attributes?.done === 'true' && attributes?.duration}
|
||||
{$i18n.t('Thought for {{DURATION}}', {
|
||||
DURATION: dayjs.duration(attributes.duration, 'seconds').humanize()
|
||||
})}
|
||||
{:else}
|
||||
{$i18n.t('Thinking...')}
|
||||
{/if}
|
||||
{:else if attributes?.type === 'code_interpreter'}
|
||||
{#if attributes?.done === 'true'}
|
||||
{$i18n.t('Analyzed')}
|
||||
{:else}
|
||||
{$i18n.t('Analyzing...')}
|
||||
{/if}
|
||||
{:else}
|
||||
{title}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex self-center translate-y-[1px]">
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
{#if dismissible}
|
||||
<div class=" absolute -top-1 -right-1">
|
||||
<button
|
||||
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
|
||||
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition"
|
||||
type="button"
|
||||
on:click|stopPropagation={() => {
|
||||
dispatch('dismiss');
|
||||
|
||||
@@ -3,68 +3,49 @@
|
||||
|
||||
export let value = '';
|
||||
export let placeholder = '';
|
||||
export let rows = 1;
|
||||
export let required = false;
|
||||
export let className =
|
||||
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none resize-none h-full';
|
||||
|
||||
export let onKeydown: Function = () => {};
|
||||
'w-full rounded-lg px-3 py-2 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none h-full';
|
||||
|
||||
let textareaElement;
|
||||
|
||||
$: if (textareaElement) {
|
||||
if (textareaElement.innerText !== value && value !== '') {
|
||||
textareaElement.innerText = value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust height on mount and after setting the element.
|
||||
onMount(async () => {
|
||||
await tick();
|
||||
resize();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// setInterveal to cehck until textareaElement is set
|
||||
const interval = setInterval(() => {
|
||||
if (textareaElement) {
|
||||
clearInterval(interval);
|
||||
resize();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle paste event to ensure only plaintext is pasted
|
||||
function handlePaste(event: ClipboardEvent) {
|
||||
event.preventDefault(); // Prevent the default paste action
|
||||
const clipboardData = event.clipboardData?.getData('text/plain'); // Get plaintext from clipboard
|
||||
|
||||
// Insert plaintext into the textarea
|
||||
document.execCommand('insertText', false, clipboardData);
|
||||
}
|
||||
const resize = () => {
|
||||
if (textareaElement) {
|
||||
textareaElement.style.height = '';
|
||||
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
contenteditable="true"
|
||||
<textarea
|
||||
bind:this={textareaElement}
|
||||
class="{className} whitespace-pre-wrap relative {value
|
||||
? !value.trim()
|
||||
? 'placeholder'
|
||||
: ''
|
||||
: 'placeholder'}"
|
||||
style="field-sizing: content; -moz-user-select: text !important;"
|
||||
on:input={() => {
|
||||
const text = textareaElement.innerText;
|
||||
if (text === '\n') {
|
||||
value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
value = text;
|
||||
bind:value
|
||||
{placeholder}
|
||||
class={className}
|
||||
style="field-sizing: content;"
|
||||
{rows}
|
||||
{required}
|
||||
on:input={(e) => {
|
||||
resize();
|
||||
}}
|
||||
on:focus={() => {
|
||||
resize();
|
||||
}}
|
||||
on:paste={handlePaste}
|
||||
on:keydown={onKeydown}
|
||||
data-placeholder={placeholder}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.placeholder::before {
|
||||
/* absolute */
|
||||
position: absolute;
|
||||
content: attr(data-placeholder);
|
||||
color: #adb5bd;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
pointer-events: none;
|
||||
touch-action: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'w-4 h-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2.25 6a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V6Zm3.97.97a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06l-2.25 2.25a.75.75 0 0 1-1.06-1.06l1.72-1.72-1.72-1.72a.75.75 0 0 1 0-1.06Zm4.28 4.28a.75.75 0 0 0 0 1.5h3a.75.75 0 0 0 0-1.5h-3Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={className}
|
||||
stroke-width={strokeWidth}
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m3 16 5-7 6 6.5m6.5 2.5L16 13l-4.286 6M14 10h.01M4 19h16a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -10,7 +10,7 @@
|
||||
let showShortcuts = false;
|
||||
</script>
|
||||
|
||||
<div class=" hidden lg:flex fixed bottom-0 right-0 px-2 py-2 z-20">
|
||||
<div class=" hidden lg:flex fixed bottom-0 right-0 px-1 py-1 z-20">
|
||||
<button
|
||||
id="show-shortcuts-button"
|
||||
class="hidden"
|
||||
@@ -29,7 +29,7 @@
|
||||
>
|
||||
<Tooltip content={$i18n.t('Help')} placement="left">
|
||||
<button
|
||||
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-5 flex items-center justify-center text-[0.7rem] rounded-full"
|
||||
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 size-4 flex items-center justify-center text-[0.7rem] rounded-full"
|
||||
>
|
||||
?
|
||||
</button>
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -159,7 +162,7 @@
|
||||
|
||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
||||
<div class="my-auto">
|
||||
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
||||
{dayjs(chat.created_at * 1000).format('LLL')}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
|
||||
@@ -87,7 +87,13 @@
|
||||
};
|
||||
|
||||
const cloneChatHandler = async (id) => {
|
||||
const res = await cloneChatById(localStorage.token, id).catch((error) => {
|
||||
const res = await cloneChatById(
|
||||
localStorage.token,
|
||||
id,
|
||||
$i18n.t('Clone of {{TITLE}}', {
|
||||
TITLE: title
|
||||
})
|
||||
).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
let loading = false;
|
||||
let stopResponseFlag = false;
|
||||
|
||||
let systemTextareaElement: HTMLTextAreaElement;
|
||||
let messagesContainerElement: HTMLDivElement;
|
||||
|
||||
let showSystem = false;
|
||||
@@ -58,8 +59,29 @@
|
||||
console.log('stopResponse');
|
||||
};
|
||||
|
||||
const resizeSystemTextarea = async () => {
|
||||
await tick();
|
||||
if (systemTextareaElement) {
|
||||
systemTextareaElement.style.height = '';
|
||||
systemTextareaElement.style.height = Math.min(systemTextareaElement.scrollHeight, 555) + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
$: if (showSystem) {
|
||||
resizeSystemTextarea();
|
||||
}
|
||||
|
||||
const chatCompletionHandler = async () => {
|
||||
if (selectedModelId === '') {
|
||||
toast.error($i18n.t('Please select a model.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const model = $models.find((model) => model.id === selectedModelId);
|
||||
if (!model) {
|
||||
selectedModelId = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const [res, controller] = await chatCompletion(
|
||||
localStorage.token,
|
||||
@@ -258,10 +280,13 @@
|
||||
<div slot="content">
|
||||
<div class="pt-1 px-1.5">
|
||||
<textarea
|
||||
id="system-textarea"
|
||||
bind:this={systemTextareaElement}
|
||||
class="w-full h-full bg-transparent resize-none outline-none text-sm"
|
||||
bind:value={system}
|
||||
placeholder={$i18n.t("You're a helpful assistant.")}
|
||||
on:input={() => {
|
||||
resizeSystemTextarea();
|
||||
}}
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext } from 'svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let message;
|
||||
export let idx;
|
||||
|
||||
export let onDelete;
|
||||
|
||||
let textAreaElement: HTMLTextAreaElement;
|
||||
|
||||
onMount(() => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 group">
|
||||
<div class="flex items-start pt-1">
|
||||
<div
|
||||
class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left rounded-lg transition"
|
||||
>
|
||||
{$i18n.t(message.role)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<!-- $i18n.t('a user') -->
|
||||
<!-- $i18n.t('an assistant') -->
|
||||
<textarea
|
||||
id="{message.role}-{idx}-textarea"
|
||||
bind:this={textAreaElement}
|
||||
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
|
||||
placeholder={$i18n.t(`Enter {{role}} message here`, {
|
||||
role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
|
||||
})}
|
||||
rows="1"
|
||||
on:input={(e) => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
}}
|
||||
on:focus={(e) => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
|
||||
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
|
||||
}}
|
||||
bind:value={message.content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class=" pt-1">
|
||||
<button
|
||||
class=" group-hover:text-gray-500 dark:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
on:click={() => {
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,77 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import Message from './Message.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let messages = [];
|
||||
let textAreaElement: HTMLTextAreaElement;
|
||||
onMount(() => {
|
||||
messages.forEach((message, idx) => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="py-3 space-y-3">
|
||||
{#each messages as message, idx}
|
||||
<div class="flex gap-2 group">
|
||||
<div class="flex items-start pt-1">
|
||||
<div
|
||||
class="px-2 py-1 text-sm font-semibold uppercase min-w-[6rem] text-left rounded-lg transition"
|
||||
>
|
||||
{$i18n.t(message.role)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<!-- $i18n.t('a user') -->
|
||||
<!-- $i18n.t('an assistant') -->
|
||||
<textarea
|
||||
id="{message.role}-{idx}-textarea"
|
||||
bind:this={textAreaElement}
|
||||
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
|
||||
placeholder={$i18n.t(`Enter {{role}} message here`, {
|
||||
role: message.role === 'user' ? $i18n.t('a user') : $i18n.t('an assistant')
|
||||
})}
|
||||
rows="1"
|
||||
on:input={(e) => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
}}
|
||||
on:focus={(e) => {
|
||||
textAreaElement.style.height = '';
|
||||
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
|
||||
|
||||
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
|
||||
}}
|
||||
bind:value={message.content}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class=" pt-1">
|
||||
<button
|
||||
class=" group-hover:text-gray-500 dark:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
on:click={() => {
|
||||
messages = messages.filter((message, messageIdx) => messageIdx !== idx);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Message
|
||||
{message}
|
||||
{idx}
|
||||
onDelete={() => {
|
||||
messages = messages.filter((message, messageIdx) => messageIdx !== idx);
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
const deleteHandler = async (item) => {
|
||||
const res = await deleteKnowledgeById(localStorage.token, item.id).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
description,
|
||||
accessControl
|
||||
).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
||||
<AccessControl bind:accessControl />
|
||||
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
|
||||
try {
|
||||
const uploadedFile = await uploadFile(localStorage.token, file).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
toast.error($i18n.t('Failed to upload file.'));
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -339,7 +339,7 @@
|
||||
const syncDirectoryHandler = async () => {
|
||||
if ((knowledge?.files ?? []).length > 0) {
|
||||
const res = await resetKnowledgeById(localStorage.token, id).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -357,7 +357,7 @@
|
||||
const addFileHandler = async (fileId) => {
|
||||
const updatedKnowledge = await addFileToKnowledgeById(localStorage.token, id, fileId).catch(
|
||||
(e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
@@ -386,7 +386,7 @@
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error in deleteFileHandler:', e);
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -395,7 +395,7 @@
|
||||
const content = selectedFile.data.content;
|
||||
|
||||
const res = updateFileDataContentById(localStorage.token, fileId, content).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
const updatedKnowledge = await updateFileFromKnowledgeById(
|
||||
@@ -403,7 +403,7 @@
|
||||
id,
|
||||
fileId
|
||||
).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res && updatedKnowledge) {
|
||||
@@ -430,7 +430,7 @@
|
||||
description: knowledge.description,
|
||||
access_control: knowledge.access_control
|
||||
}).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
});
|
||||
|
||||
if (res) {
|
||||
@@ -522,7 +522,7 @@
|
||||
id = $page.params.id;
|
||||
|
||||
const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
const deleteModelHandler = async (model) => {
|
||||
const res = await deleteModelById(localStorage.token, model.id).catch((e) => {
|
||||
toast.error(e);
|
||||
toast.error(`${e}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
@@ -531,7 +531,7 @@
|
||||
|
||||
<div class="my-2">
|
||||
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
||||
<AccessControl bind:accessControl />
|
||||
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {saving
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full {saving
|
||||
? ' cursor-not-allowed'
|
||||
: ''}"
|
||||
type="submit"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// Import all locales
|
||||
import 'dayjs/locale/af';
|
||||
import 'dayjs/locale/am';
|
||||
import 'dayjs/locale/ar';
|
||||
import 'dayjs/locale/az';
|
||||
import 'dayjs/locale/be';
|
||||
import 'dayjs/locale/bg';
|
||||
import 'dayjs/locale/bi';
|
||||
import 'dayjs/locale/bm';
|
||||
import 'dayjs/locale/bn';
|
||||
import 'dayjs/locale/bo';
|
||||
import 'dayjs/locale/br';
|
||||
import 'dayjs/locale/bs';
|
||||
import 'dayjs/locale/ca';
|
||||
import 'dayjs/locale/cs';
|
||||
import 'dayjs/locale/cv';
|
||||
import 'dayjs/locale/cy';
|
||||
import 'dayjs/locale/da';
|
||||
import 'dayjs/locale/de';
|
||||
import 'dayjs/locale/dv';
|
||||
import 'dayjs/locale/el';
|
||||
import 'dayjs/locale/en';
|
||||
import 'dayjs/locale/eo';
|
||||
import 'dayjs/locale/es';
|
||||
import 'dayjs/locale/eu';
|
||||
import 'dayjs/locale/fa';
|
||||
import 'dayjs/locale/fi';
|
||||
import 'dayjs/locale/fo';
|
||||
import 'dayjs/locale/fr';
|
||||
import 'dayjs/locale/fy';
|
||||
import 'dayjs/locale/ga';
|
||||
import 'dayjs/locale/gd';
|
||||
import 'dayjs/locale/gl';
|
||||
import 'dayjs/locale/gu';
|
||||
import 'dayjs/locale/he';
|
||||
import 'dayjs/locale/hi';
|
||||
import 'dayjs/locale/hr';
|
||||
import 'dayjs/locale/ht';
|
||||
import 'dayjs/locale/hu';
|
||||
import 'dayjs/locale/id';
|
||||
import 'dayjs/locale/is';
|
||||
import 'dayjs/locale/it';
|
||||
import 'dayjs/locale/ja';
|
||||
import 'dayjs/locale/jv';
|
||||
import 'dayjs/locale/ka';
|
||||
import 'dayjs/locale/kk';
|
||||
import 'dayjs/locale/km';
|
||||
import 'dayjs/locale/kn';
|
||||
import 'dayjs/locale/ko';
|
||||
import 'dayjs/locale/ku';
|
||||
import 'dayjs/locale/ky';
|
||||
import 'dayjs/locale/lb';
|
||||
import 'dayjs/locale/lo';
|
||||
import 'dayjs/locale/lt';
|
||||
import 'dayjs/locale/lv';
|
||||
import 'dayjs/locale/me';
|
||||
import 'dayjs/locale/mi';
|
||||
import 'dayjs/locale/mk';
|
||||
import 'dayjs/locale/ml';
|
||||
import 'dayjs/locale/mn';
|
||||
import 'dayjs/locale/mr';
|
||||
import 'dayjs/locale/ms';
|
||||
import 'dayjs/locale/mt';
|
||||
import 'dayjs/locale/my';
|
||||
import 'dayjs/locale/nb';
|
||||
import 'dayjs/locale/ne';
|
||||
import 'dayjs/locale/nl';
|
||||
import 'dayjs/locale/nn';
|
||||
import 'dayjs/locale/pl';
|
||||
import 'dayjs/locale/pt';
|
||||
import 'dayjs/locale/ro';
|
||||
import 'dayjs/locale/ru';
|
||||
import 'dayjs/locale/rw';
|
||||
import 'dayjs/locale/sd';
|
||||
import 'dayjs/locale/se';
|
||||
import 'dayjs/locale/si';
|
||||
import 'dayjs/locale/sk';
|
||||
import 'dayjs/locale/sl';
|
||||
import 'dayjs/locale/sq';
|
||||
import 'dayjs/locale/sr';
|
||||
import 'dayjs/locale/ss';
|
||||
import 'dayjs/locale/sv';
|
||||
import 'dayjs/locale/sw';
|
||||
import 'dayjs/locale/ta';
|
||||
import 'dayjs/locale/te';
|
||||
import 'dayjs/locale/tet';
|
||||
import 'dayjs/locale/tg';
|
||||
import 'dayjs/locale/th';
|
||||
import 'dayjs/locale/tk';
|
||||
import 'dayjs/locale/tlh';
|
||||
import 'dayjs/locale/tr';
|
||||
import 'dayjs/locale/tzl';
|
||||
import 'dayjs/locale/tzm';
|
||||
import 'dayjs/locale/uk';
|
||||
import 'dayjs/locale/ur';
|
||||
import 'dayjs/locale/uz';
|
||||
import 'dayjs/locale/vi';
|
||||
import 'dayjs/locale/yo';
|
||||
import 'dayjs/locale/zh';
|
||||
import 'dayjs/locale/et';
|
||||
|
||||
export default dayjs;
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "هل تملك حساب ؟",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "مساعد",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "و",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "و أنشئ رابط مشترك جديد.",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
|
||||
"Clone": "استنساخ",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "أغلق",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "مجموعة",
|
||||
"Color": "",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "اتصالات",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "الاتصال",
|
||||
"Content Extraction": "",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "الإفتراضي",
|
||||
"Default (Open AI)": "",
|
||||
"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "النموذج الافتراضي",
|
||||
"Default model updated": "الإفتراضي تحديث الموديل",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "أدخل الChunk Overlap",
|
||||
"Enter Chunk Size": "أدخل Chunk الحجم",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "أدخل عنوان URL ل Github Raw",
|
||||
"Enter Google PSE API Key": "أدخل مفتاح واجهة برمجة تطبيقات PSE من Google",
|
||||
"Enter Google PSE Engine Id": "أدخل معرف محرك PSE من Google",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "(e.g. 50) أدخل عدد الخطوات",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "",
|
||||
"Enter Scheduler (e.g. Karras)": "",
|
||||
"Enter Score": "أدخل النتيجة",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "تجريبي",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "تصدير",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "عقوبة التردد",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "",
|
||||
"Function deleted successfully": "",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "",
|
||||
"General": "عام",
|
||||
"General Settings": "الاعدادات العامة",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generating search query": "إنشاء استعلام بحث",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "الساعة:الدقائق صباحا/مساء",
|
||||
"Haptic Feedback": "",
|
||||
"has no conversations.": "ليس لديه محادثات.",
|
||||
"Hello, {{name}}": " {{name}} مرحبا",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "توليد الصور (تجريبي)",
|
||||
"Image Generation Engine": "محرك توليد الصور",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "المزيد",
|
||||
"Name": "الأسم",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "دردشة جديدة",
|
||||
"New Folder": "",
|
||||
"New Password": "كلمة المرور الجديدة",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "موقف ايجابي",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "أخر 30 يوم",
|
||||
"Previous 7 days": "أخر 7 أيام",
|
||||
"Profile Image": "صورة الملف الشخصي",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "موجه (على سبيل المثال: أخبرني بحقيقة ممتعة عن الإمبراطورية الرومانية)",
|
||||
"Prompt Content": "محتوى عاجل",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "أقراء لي",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "سجل صوت",
|
||||
"Redirecting you to OpenWebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "أبحث حث",
|
||||
"Search Result Count": "عدد نتائج البحث",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "",
|
||||
"SearchApi API Key": "",
|
||||
"SearchApi Engine": "",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
|
||||
"Thorough explanation": "شرح شامل",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "",
|
||||
"Tika Server URL required.": "",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Web تحميل اعدادات",
|
||||
"Web Search": "بحث الويب",
|
||||
"Web Search Engine": "محرك بحث الويب",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "Webhook الرابط",
|
||||
"WebUI Settings": "WebUI اعدادات",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "لا تملك محادثات محفوظه",
|
||||
"You have shared this chat": "تم مشاركة هذه المحادثة",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Вече имате акаунт? ",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "асистент",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "и",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "и създай нов общ линк.",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
|
||||
"Clone": "Клонинг",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Затвори",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Колекция",
|
||||
"Color": "",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Връзки",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "Съдържание",
|
||||
"Content Extraction": "",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "По подразбиране",
|
||||
"Default (Open AI)": "",
|
||||
"Default (SentenceTransformers)": "По подразбиране (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "Модел по подразбиране",
|
||||
"Default model updated": "Моделът по подразбиране е обновен",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Въведете Chunk Overlap",
|
||||
"Enter Chunk Size": "Въведете Chunk Size",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "Въведете URL адреса на Github Raw",
|
||||
"Enter Google PSE API Key": "Въведете Google PSE API ключ",
|
||||
"Enter Google PSE Engine Id": "Въведете идентификатор на двигателя на Google PSE",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Въведете брой стъпки (напр. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "",
|
||||
"Enter Scheduler (e.g. Karras)": "",
|
||||
"Enter Score": "Въведете оценка",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Експериментално",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "Износ",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "Неуспешно създаване на API ключ.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "Наказание за честота",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "",
|
||||
"Function deleted successfully": "",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "",
|
||||
"General": "Основни",
|
||||
"General Settings": "Основни Настройки",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generating search query": "Генериране на заявка за търсене",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "h:mm a",
|
||||
"Haptic Feedback": "",
|
||||
"has no conversations.": "няма разговори.",
|
||||
"Hello, {{name}}": "Здравей, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Генерация на изображения (Експериментално)",
|
||||
"Image Generation Engine": "Двигател за генериране на изображения",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "Повече",
|
||||
"Name": "Име",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Нов чат",
|
||||
"New Folder": "",
|
||||
"New Password": "Нова парола",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "Позитивна ативност",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "Предыдущите 30 дни",
|
||||
"Previous 7 days": "Предыдущите 7 дни",
|
||||
"Profile Image": "Профилна снимка",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Промпт (напр. Обмисли ме забавна факт за Римската империя)",
|
||||
"Prompt Content": "Съдържание на промпта",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "Прочети на Голос",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Записване на глас",
|
||||
"Redirecting you to OpenWebUI Community": "Пренасочване към OpenWebUI общността",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "Търси Промптове",
|
||||
"Search Result Count": "Брой резултати от търсенето",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "",
|
||||
"SearchApi API Key": "",
|
||||
"SearchApi Engine": "",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
|
||||
"Thorough explanation": "Това е подробно описание.",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "",
|
||||
"Tika Server URL required.": "",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Настройки за зареждане на уеб",
|
||||
"Web Search": "Търсене в уеб",
|
||||
"Web Search Engine": "Уеб търсачка",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "Уебхук URL",
|
||||
"WebUI Settings": "WebUI Настройки",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "Нямате архивирани разговори.",
|
||||
"You have shared this chat": "Вие сте споделели този чат",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "আগে থেকেই একাউন্ট আছে?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "একটা এসিস্ট্যান্ট",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "এবং",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "এবং একটি নতুন শেয়ারে লিংক তৈরি করুন.",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
|
||||
"Clone": "ক্লোন",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "বন্ধ",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "সংগ্রহ",
|
||||
"Color": "",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "কানেকশনগুলো",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "বিষয়বস্তু",
|
||||
"Content Extraction": "",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "ডিফল্ট",
|
||||
"Default (Open AI)": "",
|
||||
"Default (SentenceTransformers)": "ডিফল্ট (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "ডিফল্ট মডেল",
|
||||
"Default model updated": "ডিফল্ট মডেল আপডেট হয়েছে",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "চাঙ্ক ওভারল্যাপ লিখুন",
|
||||
"Enter Chunk Size": "চাংক সাইজ লিখুন",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "গিটহাব কাঁচা URL লিখুন",
|
||||
"Enter Google PSE API Key": "গুগল পিএসই এপিআই কী লিখুন",
|
||||
"Enter Google PSE Engine Id": "গুগল পিএসই ইঞ্জিন আইডি লিখুন",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "ধাপের সংখ্যা দিন (যেমন: 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "",
|
||||
"Enter Scheduler (e.g. Karras)": "",
|
||||
"Enter Score": "স্কোর দিন",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "পরিক্ষামূলক",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "রপ্তানি",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "ফ্রিকোয়েন্সি পেনাল্টি",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "",
|
||||
"Function deleted successfully": "",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "",
|
||||
"General": "সাধারণ",
|
||||
"General Settings": "সাধারণ সেটিংসমূহ",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generating search query": "অনুসন্ধান ক্যোয়ারী তৈরি করা হচ্ছে",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "h:mm a",
|
||||
"Haptic Feedback": "",
|
||||
"has no conversations.": "কোন কনভার্সেশন আছে না।",
|
||||
"Hello, {{name}}": "হ্যালো, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "ইমেজ জেনারেশন (পরিক্ষামূলক)",
|
||||
"Image Generation Engine": "ইমেজ জেনারেশন ইঞ্জিন",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "আরো",
|
||||
"Name": "নাম",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "নতুন চ্যাট",
|
||||
"New Folder": "",
|
||||
"New Password": "নতুন পাসওয়ার্ড",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "পজিটিভ আক্রমণ",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "পূর্ব ৩০ দিন",
|
||||
"Previous 7 days": "পূর্ব ৭ দিন",
|
||||
"Profile Image": "প্রোফাইল ইমেজ",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "প্রম্প্ট (উদাহরণস্বরূপ, আমি রোমান ইমপার্টের সম্পর্কে একটি উপস্থিতি জানতে বল)",
|
||||
"Prompt Content": "প্রম্পট কন্টেন্ট",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "পড়াশোনা করুন",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "ভয়েস রেকর্ড করুন",
|
||||
"Redirecting you to OpenWebUI Community": "আপনাকে OpenWebUI কমিউনিটিতে পাঠানো হচ্ছে",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "প্রম্পটসমূহ অনুসন্ধান করুন",
|
||||
"Search Result Count": "অনুসন্ধানের ফলাফল গণনা",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "",
|
||||
"SearchApi API Key": "",
|
||||
"SearchApi Engine": "",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
|
||||
"Thorough explanation": "পুঙ্খানুপুঙ্খ ব্যাখ্যা",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "",
|
||||
"Tika Server URL required.": "",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "ওয়েব লোডার সেটিংস",
|
||||
"Web Search": "ওয়েব অনুসন্ধান",
|
||||
"Web Search Engine": "ওয়েব সার্চ ইঞ্জিন",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "ওয়েবহুক URL",
|
||||
"WebUI Settings": "WebUI সেটিংসমূহ",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "আপনার কোনও আর্কাইভ করা কথোপকথন নেই।",
|
||||
"You have shared this chat": "আপনি এই চ্যাটটি শেয়ার করেছেন",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"(e.g. `sh webui.sh --api`)": "(p. ex. `sh webui.sh --api`)",
|
||||
"(latest)": "(últim)",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{COUNT}} Replies": "{{COUNT}} respostes",
|
||||
"{{user}}'s Chats": "Els xats de {{user}}",
|
||||
"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
|
||||
"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
|
||||
@@ -35,7 +35,7 @@
|
||||
"Add Group": "Afegir grup",
|
||||
"Add Memory": "Afegir memòria",
|
||||
"Add Model": "Afegir un model",
|
||||
"Add Reaction": "",
|
||||
"Add Reaction": "Afegir reacció",
|
||||
"Add Tag": "Afegir etiqueta",
|
||||
"Add Tags": "Afegir etiquetes",
|
||||
"Add text content": "Afegir contingut de text",
|
||||
@@ -51,7 +51,7 @@
|
||||
"Advanced Params": "Paràmetres avançats",
|
||||
"All Documents": "Tots els documents",
|
||||
"All models deleted successfully": "Tots els models s'han eliminat correctament",
|
||||
"Allow Chat Controls": "",
|
||||
"Allow Chat Controls": "Permetre els controls de xat",
|
||||
"Allow Chat Delete": "Permetre eliminar el xat",
|
||||
"Allow Chat Deletion": "Permetre la supressió del xat",
|
||||
"Allow Chat Edit": "Permetre editar el xat",
|
||||
@@ -60,18 +60,21 @@
|
||||
"Allow Temporary Chat": "Permetre el xat temporal",
|
||||
"Allow User Location": "Permetre la ubicació de l'usuari",
|
||||
"Allow Voice Interruption in Call": "Permetre la interrupció de la veu en una trucada",
|
||||
"Allowed Endpoints": "",
|
||||
"Allowed Endpoints": "Punts d'accés permesos",
|
||||
"Already have an account?": "Ja tens un compte?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "Alternativa al top_p, i pretén garantir un equilibri de qualitat i varietat. El paràmetre p representa la probabilitat mínima que es consideri un token, en relació amb la probabilitat del token més probable. Per exemple, amb p=0,05 i el token més probable amb una probabilitat de 0,9, es filtren els logits amb un valor inferior a 0,045. (Per defecte: 0.0)",
|
||||
"Always": "",
|
||||
"Amazing": "Al·lucinant",
|
||||
"an assistant": "un assistent",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "i",
|
||||
"and {{COUNT}} more": "i {{COUNT}} més",
|
||||
"and create a new shared link.": "i crear un nou enllaç compartit.",
|
||||
"API Base URL": "URL Base de l'API",
|
||||
"API Key": "clau API",
|
||||
"API Key created.": "clau API creada.",
|
||||
"API Key Endpoint Restrictions": "",
|
||||
"API Key Endpoint Restrictions": "Restriccions del punt d'accés de la Clau API",
|
||||
"API keys": "Claus de l'API",
|
||||
"Application DN": "DN d'aplicació",
|
||||
"Application DN Password": "Contrasenya del DN d'aplicació",
|
||||
@@ -91,7 +94,7 @@
|
||||
"Assistant": "Assistent",
|
||||
"Attach file": "Adjuntar arxiu",
|
||||
"Attention to detail": "Atenció al detall",
|
||||
"Attribute for Mail": "",
|
||||
"Attribute for Mail": "Atribut per al Correu",
|
||||
"Attribute for Username": "Atribut per al Nom d'usuari",
|
||||
"Audio": "Àudio",
|
||||
"August": "Agost",
|
||||
@@ -168,10 +171,12 @@
|
||||
"Click on the user role button to change a user's role.": "Clica sobre el botó de rol d'usuari per canviar el rol d'un usuari.",
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Permís d'escriptura al porta-retalls denegat. Comprova els ajustos de navegador per donar l'accés necessari.",
|
||||
"Clone": "Clonar",
|
||||
"Clone Chat": "",
|
||||
"Clone Chat": "Clonar el xat",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Tancar",
|
||||
"Code execution": "Execució de codi",
|
||||
"Code formatted successfully": "Codi formatat correctament",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Col·lecció",
|
||||
"Color": "Color",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "Confirma la teva acció",
|
||||
"Confirm your new password": "Confirma la teva nova contrasenya",
|
||||
"Connections": "Connexions",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "Restringeix l'esforç de raonament dels models de raonament. Només aplicable a models de raonament de proveïdors específics que donen suport a l'esforç de raonament. (Per defecte: mitjà)",
|
||||
"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
|
||||
"Content": "Contingut",
|
||||
"Content Extraction": "Extracció de contingut",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Per defecte",
|
||||
"Default (Open AI)": "Per defecte (Open AI)",
|
||||
"Default (SentenceTransformers)": "Per defecte (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "Model per defecte",
|
||||
"Default model updated": "Model per defecte actualitzat",
|
||||
"Default Models": "Models per defecte",
|
||||
@@ -316,7 +323,7 @@
|
||||
"Embedding Model": "Model d'incrustació",
|
||||
"Embedding Model Engine": "Motor de model d'incrustació",
|
||||
"Embedding model set to \"{{embedding_model}}\"": "Model d'incrustació configurat a \"{{embedding_model}}\"",
|
||||
"Enable API Key": "",
|
||||
"Enable API Key": "Activar la Clau API",
|
||||
"Enable autocomplete generation for chat messages": "Activar la generació automàtica per als missatges del xat",
|
||||
"Enable Community Sharing": "Activar l'ús compartit amb la comunitat",
|
||||
"Enable Google Drive": "Activar Google Drive",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Introdueix la mida de solapament de blocs",
|
||||
"Enter Chunk Size": "Introdueix la mida del bloc",
|
||||
"Enter description": "Introdueix la descripció",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "Introdueix l'URL en brut de Github",
|
||||
"Enter Google PSE API Key": "Introdueix la clau API de Google PSE",
|
||||
"Enter Google PSE Engine Id": "Introdueix l'identificador del motor PSE de Google",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
|
||||
"Enter Number of Steps (e.g. 50)": "Introdueix el nombre de passos (p. ex. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "Entra l'URL (p. ex. https://user:password@host:port)",
|
||||
"Enter reasoning effort": "Introdueix l'esforç de raonament",
|
||||
"Enter Sampler (e.g. Euler a)": "Introdueix el mostrejador (p.ex. Euler a)",
|
||||
"Enter Scheduler (e.g. Karras)": "Entra el programador (p.ex. Karras)",
|
||||
"Enter Score": "Introdueix la puntuació",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "Error en accedir a Google Drive: {{error}}",
|
||||
"Error uploading file: {{error}}": "Error en pujar l'arxiu: {{error}}",
|
||||
"Evaluations": "Avaluacions",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Exemple: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Exemple: TOTS",
|
||||
"Example: mail": "",
|
||||
"Example: mail": "Exemple: mail",
|
||||
"Example: ou=users,dc=foo,dc=example": "Exemple: ou=users,dc=foo,dc=example",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "Exemple: sAMAccountName o uid o userPrincipalName",
|
||||
"Exclude": "Excloure",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Experimental",
|
||||
"Explore the cosmos": "Explorar el cosmos",
|
||||
"Export": "Exportar",
|
||||
@@ -412,11 +423,12 @@
|
||||
"External Models": "Models externs",
|
||||
"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
|
||||
"Failed to create API Key.": "No s'ha pogut crear la clau API.",
|
||||
"Failed to fetch models": "No s'han pogut obtenir els models",
|
||||
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
|
||||
"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
|
||||
"Failed to update settings": "No s'han pogut actualitzar les preferències",
|
||||
"Failed to upload file.": "No s'ha pogut pujar l'arxiu.",
|
||||
"Features Permissions": "",
|
||||
"Features Permissions": "Permisos de les característiques",
|
||||
"February": "Febrer",
|
||||
"Feedback History": "Històric de comentaris",
|
||||
"Feedbacks": "Comentaris",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
|
||||
"Frequency Penalty": "Penalització per freqüència",
|
||||
"Function": "Funció",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "La funció s'ha creat correctament",
|
||||
"Function deleted successfully": "La funció s'ha eliminat correctament",
|
||||
"Function Description": "Descripció de la funció",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "Les funcions s'han importat correctament",
|
||||
"General": "General",
|
||||
"General Settings": "Preferències generals",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "Generar imatge",
|
||||
"Generating search query": "Generant consulta",
|
||||
"Get started": "Començar",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "Nom del grup",
|
||||
"Group updated successfully": "Grup actualitzat correctament",
|
||||
"Groups": "Grups",
|
||||
"h:mm a": "h:mm a",
|
||||
"Haptic Feedback": "Retorn hàptic",
|
||||
"has no conversations.": "no té converses.",
|
||||
"Hello, {{name}}": "Hola, {{name}}",
|
||||
@@ -491,15 +504,14 @@
|
||||
"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Afirmo que he llegit i entenc les implicacions de la meva acció. Soc conscient dels riscos associats a l'execució de codi arbitrari i he verificat la fiabilitat de la font.",
|
||||
"ID": "ID",
|
||||
"Ignite curiosity": "Despertar la curiositat",
|
||||
"Image": "",
|
||||
"Image": "Imatge",
|
||||
"Image Compression": "Compressió d'imatges",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation": "Generació d'imatges",
|
||||
"Image Generation (Experimental)": "Generació d'imatges (Experimental)",
|
||||
"Image Generation Engine": "Motor de generació d'imatges",
|
||||
"Image Max Compression Size": "Mida màxima de la compressió d'imatges",
|
||||
"Image Prompt Generation": "",
|
||||
"Image Prompt Generation Prompt": "",
|
||||
"Image Prompt Generation": "Generació d'indicacions d'imatge",
|
||||
"Image Prompt Generation Prompt": "Indicació per a la generació d'indicacions d'imatge",
|
||||
"Image Settings": "Preferències d'imatges",
|
||||
"Images": "Imatges",
|
||||
"Import Chats": "Importar xats",
|
||||
@@ -520,7 +532,7 @@
|
||||
"Interface": "Interfície",
|
||||
"Invalid file format.": "Format d'arxiu no vàlid.",
|
||||
"Invalid Tag": "Etiqueta no vàlida",
|
||||
"is typing...": "",
|
||||
"is typing...": "està escrivint...",
|
||||
"January": "Gener",
|
||||
"Jina API Key": "Clau API de Jina",
|
||||
"join our Discord for help.": "uneix-te al nostre Discord per obtenir ajuda.",
|
||||
@@ -545,7 +557,7 @@
|
||||
"Language": "Idioma",
|
||||
"Last Active": "Activitat recent",
|
||||
"Last Modified": "Modificació",
|
||||
"Last reply": "",
|
||||
"Last reply": "Darrera resposta",
|
||||
"LDAP": "LDAP",
|
||||
"LDAP server updated": "Servidor LDAP actualitzat",
|
||||
"Leaderboard": "Tauler de classificació",
|
||||
@@ -556,7 +568,7 @@
|
||||
"Leave empty to use the default prompt, or enter a custom prompt": "Deixa-ho en blanc per utilitzar la indicació predeterminada o introdueix una indicació personalitzada",
|
||||
"Light": "Clar",
|
||||
"Listening...": "Escoltant...",
|
||||
"Llama.cpp": "",
|
||||
"Llama.cpp": "Llama.cpp",
|
||||
"LLMs can make mistakes. Verify important information.": "Els models de llenguatge poden cometre errors. Verifica la informació important.",
|
||||
"Local": "Local",
|
||||
"Local Models": "Models locals",
|
||||
@@ -567,7 +579,7 @@
|
||||
"Make sure to export a workflow.json file as API format from ComfyUI.": "Assegura't d'exportar un fitxer workflow.json com a format API des de ComfyUI.",
|
||||
"Manage": "Gestionar",
|
||||
"Manage Arena Models": "Gestionar els models de l'Arena",
|
||||
"Manage Models": "",
|
||||
"Manage Models": "Gestionar els models",
|
||||
"Manage Ollama": "Gestionar Ollama",
|
||||
"Manage Ollama API Connections": "Gestionar les connexions a l'API d'Ollama",
|
||||
"Manage OpenAI API Connections": "Gestionar les connexions a l'API d'OpenAI",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Eta de Mirostat",
|
||||
"Mirostat Tau": "Tau de Mirostat",
|
||||
"MMMM DD, YYYY": "DD de MMMM, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "DD de MMMM, YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "DD de MMMM, YYYY HH:mm:ss, A",
|
||||
"Model": "Model",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "El model '{{modelName}}' s'ha descarregat correctament.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
|
||||
@@ -621,8 +630,9 @@
|
||||
"More": "Més",
|
||||
"Name": "Nom",
|
||||
"Name your knowledge base": "Anomena la teva base de coneixement",
|
||||
"Native": "",
|
||||
"New Chat": "Nou xat",
|
||||
"New Folder": "",
|
||||
"New Folder": "Nova carpeta",
|
||||
"New Password": "Nova contrasenya",
|
||||
"new-channel": "nou-canal",
|
||||
"No content found": "No s'ha trobat contingut",
|
||||
@@ -633,7 +643,7 @@
|
||||
"No files found.": "No s'han trobat arxius.",
|
||||
"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 inference engine with management support found": "",
|
||||
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
|
||||
"No knowledge found": "No s'ha trobat Coneixement",
|
||||
"No model IDs": "No hi ha IDs de model",
|
||||
"No models found": "No s'han trobat models",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "Si us plau, entra una indicació",
|
||||
"Please fill in all fields.": "Emplena tots els camps, si us plau.",
|
||||
"Please select a model first.": "Si us plau, selecciona un model primer",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "Si us plau, selecciona una raó",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Actitud positiva",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "30 dies anteriors",
|
||||
"Previous 7 days": "7 dies anteriors",
|
||||
"Profile Image": "Imatge de perfil",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Indicació (p.ex. Digues-me quelcom divertit sobre l'Imperi Romà)",
|
||||
"Prompt Content": "Contingut de la indicació",
|
||||
"Prompt created successfully": "Indicació creada correctament",
|
||||
@@ -738,8 +750,9 @@
|
||||
"RAG Template": "Plantilla RAG",
|
||||
"Rating": "Valoració",
|
||||
"Re-rank models by topic similarity": "Reclassificar els models per similitud de temes",
|
||||
"Read": "",
|
||||
"Read": "Llegit",
|
||||
"Read Aloud": "Llegir en veu alta",
|
||||
"Reasoning Effort": "Esforç de raonament",
|
||||
"Record voice": "Enregistrar la veu",
|
||||
"Redirecting you to OpenWebUI Community": "Redirigint-te a la comunitat OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "Redueix la probabilitat de generar ximpleries. Un valor més alt (p. ex. 100) donarà respostes més diverses, mentre que un valor més baix (p. ex. 10) serà més conservador. (Per defecte: 40)",
|
||||
@@ -754,7 +767,7 @@
|
||||
"Rename": "Canviar el nom",
|
||||
"Reorder Models": "Reordenar els models",
|
||||
"Repeat Last N": "Repeteix els darrers N",
|
||||
"Reply in Thread": "",
|
||||
"Reply in Thread": "Respondre al fil",
|
||||
"Request Mode": "Mode de sol·licitud",
|
||||
"Reranking Model": "Model de reavaluació",
|
||||
"Reranking model disabled": "Model de reavaluació desactivat",
|
||||
@@ -763,7 +776,7 @@
|
||||
"Reset All Models": "Restablir tots els models",
|
||||
"Reset Upload Directory": "Restableix el directori de pujades",
|
||||
"Reset Vector Storage/Knowledge": "Restableix el Repositori de vectors/Coneixement",
|
||||
"Reset view": "",
|
||||
"Reset view": "Netejar la vista",
|
||||
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Les notifications de resposta no es poden activar perquè els permisos del lloc web han estat rebutjats. Comprova les preferències del navegador per donar l'accés necessari.",
|
||||
"Response splitting": "Divisió de la resposta",
|
||||
"Result": "Resultat",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "Opcions de cerca",
|
||||
"Search Prompts": "Cercar indicacions",
|
||||
"Search Result Count": "Recompte de resultats de cerca",
|
||||
"Search the web": "Cercar la web",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "Cercar eines",
|
||||
"SearchApi API Key": "Clau API de SearchApi",
|
||||
"SearchApi Engine": "Motor de SearchApi",
|
||||
@@ -816,7 +829,7 @@
|
||||
"Select a pipeline": "Seleccionar una Pipeline",
|
||||
"Select a pipeline url": "Seleccionar l'URL d'una Pipeline",
|
||||
"Select a tool": "Seleccionar una eina",
|
||||
"Select an Ollama instance": "",
|
||||
"Select an Ollama instance": "Seleccionar una instància d'Ollama",
|
||||
"Select Engine": "Seleccionar el motor",
|
||||
"Select Knowledge": "Seleccionar coneixement",
|
||||
"Select model": "Seleccionar un model",
|
||||
@@ -843,7 +856,7 @@
|
||||
"Set Scheduler": "Establir el programador",
|
||||
"Set Steps": "Establir el nombre de passos",
|
||||
"Set Task Model": "Establir el model de tasca",
|
||||
"Set the number of layers, which will be off-loaded to GPU. Increasing this value can significantly improve performance for models that are optimized for GPU acceleration but may also consume more power and GPU resources.": "",
|
||||
"Set the number of layers, which will be off-loaded to GPU. Increasing this value can significantly improve performance for models that are optimized for GPU acceleration but may also consume more power and GPU resources.": "Estableix el nombre de capes que es descarregaran a la GPU. Augmentar aquest valor pot millorar significativament el rendiment dels models optimitzats per a l'acceleració de la GPU, però també pot consumir més energia i recursos de GPU.",
|
||||
"Set the number of worker threads used for computation. This option controls how many threads are used to process incoming requests concurrently. Increasing this value can improve performance under high concurrency workloads but may also consume more CPU resources.": "Establir el nombre de fils de treball utilitzats per al càlcul. Aquesta opció controla quants fils s'utilitzen per processar les sol·licituds entrants simultàniament. Augmentar aquest valor pot millorar el rendiment amb càrregues de treball de concurrència elevada, però també pot consumir més recursos de CPU.",
|
||||
"Set Voice": "Establir la veu",
|
||||
"Set whisper model": "Establir el model whisper",
|
||||
@@ -908,7 +921,7 @@
|
||||
"The batch size determines how many text requests are processed together at once. A higher batch size can increase the performance and speed of the model, but it also requires more memory. (Default: 512)": "La mida del lot determina quantes sol·licituds de text es processen alhora. Una mida de lot més gran pot augmentar el rendiment i la velocitat del model, però també requereix més memòria. (Per defecte: 512)",
|
||||
"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Els desenvolupadors d'aquest complement són voluntaris apassionats de la comunitat. Si trobeu útil aquest complement, considereu contribuir al seu desenvolupament.",
|
||||
"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "La classificació d'avaluació es basa en el sistema de qualificació Elo i s'actualitza en temps real.",
|
||||
"The LDAP attribute that maps to the mail that users use to sign in.": "",
|
||||
"The LDAP attribute that maps to the mail that users use to sign in.": "L'atribut LDAP que s'associa al correu que els usuaris utilitzen per iniciar la sessió.",
|
||||
"The LDAP attribute that maps to the username that users use to sign in.": "L'atribut LDAP que mapeja el nom d'usuari amb l'usuari que vol iniciar sessió",
|
||||
"The leaderboard is currently in beta, and we may adjust the rating calculations as we refine the algorithm.": "La classificació està actualment en versió beta i és possible que s'ajustin els càlculs de la puntuació a mesura que es perfeccioni l'algorisme.",
|
||||
"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "La mida màxima del fitxer en MB. Si la mida del fitxer supera aquest límit, el fitxer no es carregarà.",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "Això eliminarà tots els models incloent els personalitzats i no es pot desfer",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Això restablirà la base de coneixement i sincronitzarà tots els fitxers. Vols continuar?",
|
||||
"Thorough explanation": "Explicació en detall",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "Tika",
|
||||
"Tika Server URL required.": "La URL del servidor Tika és obligatòria.",
|
||||
"Tiktoken": "Tiktoken",
|
||||
@@ -944,7 +958,7 @@
|
||||
"To access the GGUF models available for downloading,": "Per accedir als models GGUF disponibles per descarregar,",
|
||||
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedir a la WebUI, poseu-vos en contacte amb l'administrador. Els administradors poden gestionar els estats dels usuaris des del tauler d'administració.",
|
||||
"To attach knowledge base here, add them to the \"Knowledge\" workspace first.": "Per adjuntar la base de coneixement aquí, afegiu-la primer a l'espai de treball \"Coneixement\".",
|
||||
"To learn more about available endpoints, visit our documentation.": "",
|
||||
"To learn more about available endpoints, visit our documentation.": "Per obtenir més informació sobre els punts d'accés disponibles, visiteu la nostra documentació.",
|
||||
"To protect your privacy, only ratings, model IDs, tags, and metadata are shared from your feedback—your chat logs remain private and are not included.": "Per protegir la privadesa, només es comparteixen puntuacions, identificadors de models, etiquetes i metadades dels comentaris; els registres de xat romanen privats i no s'inclouen.",
|
||||
"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-les primer a l'espai de treball \"Funcions\".",
|
||||
"To select filters here, add them to the \"Functions\" workspace first.": "Per seleccionar filtres aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "Eines",
|
||||
"Tools Access": "Accés a les eines",
|
||||
"Tools are a function calling system with arbitrary code execution": "Les eines són un sistema de crida a funcions amb execució de codi arbitrari",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
|
||||
"Top K": "Top K",
|
||||
@@ -1023,7 +1038,7 @@
|
||||
"variable to have them replaced with clipboard content.": "variable per tenir-les reemplaçades amb el contingut del porta-retalls.",
|
||||
"Version": "Versió",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
|
||||
"View Replies": "",
|
||||
"View Replies": "Veure les respostes",
|
||||
"Visibility": "Visibilitat",
|
||||
"Voice": "Veu",
|
||||
"Voice Input": "Entrada de veu",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Preferències del carregador web",
|
||||
"Web Search": "Cerca la web",
|
||||
"Web Search Engine": "Motor de cerca de la web",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "Generació de consultes per a la cerca de la web",
|
||||
"Webhook URL": "URL del webhook",
|
||||
"WebUI Settings": "Preferències de WebUI",
|
||||
@@ -1054,7 +1070,7 @@
|
||||
"Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9)": "Funciona juntament amb top-k. Un valor més alt (p. ex., 0,95) donarà lloc a un text més divers, mentre que un valor més baix (p. ex., 0,5) generarà un text més concentrat i conservador. (Per defecte: 0,9)",
|
||||
"Workspace": "Espai de treball",
|
||||
"Workspace Permissions": "Permisos de l'espai de treball",
|
||||
"Write": "",
|
||||
"Write": "Escriure",
|
||||
"Write a prompt suggestion (e.g. Who are you?)": "Escriu una suggerència d'indicació (p. ex. Qui ets?)",
|
||||
"Write a summary in 50 words that summarizes [topic or keyword].": "Escriu un resum en 50 paraules que resumeixi [tema o paraula clau].",
|
||||
"Write something...": "Escriu quelcom...",
|
||||
@@ -1064,7 +1080,8 @@
|
||||
"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Només pots xatejar amb un màxim de {{maxCount}} fitxers alhora.",
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Pots personalitzar les teves interaccions amb els models de llenguatge afegint memòries mitjançant el botó 'Gestiona' que hi ha a continuació, fent-les més útils i adaptades a tu.",
|
||||
"You cannot upload an empty file.": "No es pot pujar un ariux buit.",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to access this feature.": "No tens permís per accedir a aquesta funcionalitat",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "No tens permisos per pujar arxius.",
|
||||
"You have no archived conversations.": "No tens converses arxivades.",
|
||||
"You have shared this chat": "Has compartit aquest xat",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Naa na kay account ?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "usa ka katabang",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "Ug",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
|
||||
"Clone": "",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Suod nga",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Koleksyon",
|
||||
"Color": "",
|
||||
"ComfyUI": "",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Mga koneksyon",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "Kontento",
|
||||
"Content Extraction": "",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Pinaagi sa default",
|
||||
"Default (Open AI)": "",
|
||||
"Default (SentenceTransformers)": "",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "",
|
||||
"Default model updated": "Gi-update nga default template",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Pagsulod sa block overlap",
|
||||
"Enter Chunk Size": "Isulod ang block size",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "",
|
||||
"Enter Google PSE API Key": "",
|
||||
"Enter Google PSE Engine Id": "",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Pagsulod sa gidaghanon sa mga lakang (e.g. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "",
|
||||
"Enter Scheduler (e.g. Karras)": "",
|
||||
"Enter Score": "",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Eksperimento",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "",
|
||||
"Function deleted successfully": "",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "",
|
||||
"General": "Heneral",
|
||||
"General Settings": "kinatibuk-ang mga setting",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generating search query": "",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "",
|
||||
"Haptic Feedback": "",
|
||||
"has no conversations.": "",
|
||||
"Hello, {{name}}": "Maayong buntag, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Pagmugna og hulagway (Eksperimento)",
|
||||
"Image Generation Engine": "Makina sa paghimo og imahe",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Ang modelo'{{modelName}}' malampuson nga na-download.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Ang modelo'{{modelTag}}' naa na sa pila para ma-download.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "",
|
||||
"Name": "Ngalan",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Bag-ong diskusyon",
|
||||
"New Folder": "",
|
||||
"New Password": "Bag-ong Password",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "",
|
||||
"Previous 7 days": "",
|
||||
"Profile Image": "",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
|
||||
"Prompt Content": "Ang sulod sa prompt",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Irekord ang tingog",
|
||||
"Redirecting you to OpenWebUI Community": "Gi-redirect ka sa komunidad sa OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "Pangitaa ang mga prompt",
|
||||
"Search Result Count": "",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "",
|
||||
"SearchApi API Key": "",
|
||||
"SearchApi Engine": "",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
|
||||
"Thorough explanation": "",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "",
|
||||
"Tika Server URL required.": "",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "",
|
||||
"Web Search": "",
|
||||
"Web Search Engine": "",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "",
|
||||
"WebUI Settings": "Mga Setting sa WebUI",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "",
|
||||
"You have shared this chat": "",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Už máte účet?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "asistent",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "a",
|
||||
"and {{COUNT}} more": "a {{COUNT}} další/ch",
|
||||
"and create a new shared link.": "a vytvořit nový sdílený odkaz.",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Přístup k zápisu do schránky byl zamítnut. Prosím, zkontrolujte nastavení svého prohlížeče a udělte potřebný přístup.",
|
||||
"Clone": "Klonovat",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Zavřít",
|
||||
"Code execution": "Provádění kódu",
|
||||
"Code formatted successfully": "Kód byl úspěšně naformátován.",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "",
|
||||
"Color": "Barva",
|
||||
"ComfyUI": "ComfyUI.",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "Potvrďte svoji akci",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Připojení",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "Kontaktujte administrátora pro přístup k webovému rozhraní.",
|
||||
"Content": "Obsah",
|
||||
"Content Extraction": "Extrahování obsahu",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Výchozí hodnoty nebo nastavení.",
|
||||
"Default (Open AI)": "Výchozí (Open AI)",
|
||||
"Default (SentenceTransformers)": "Výchozí (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "Výchozí model",
|
||||
"Default model updated": "Výchozí model aktualizován.",
|
||||
"Default Models": "Výchozí modely",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Zadejte překryv části",
|
||||
"Enter Chunk Size": "Zadejte velikost bloku",
|
||||
"Enter description": "Zadejte popis",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "Zadejte URL adresu Github Raw",
|
||||
"Enter Google PSE API Key": "Zadejte klíč rozhraní API Google PSE",
|
||||
"Enter Google PSE Engine Id": "Zadejte ID vyhledávacího mechanismu Google PSE",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Zadejte počet kroků (např. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "Zadejte vzorkovač (např. Euler a)",
|
||||
"Enter Scheduler (e.g. Karras)": "Zadejte plánovač (např. Karras)",
|
||||
"Enter Score": "Zadejte skóre",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "Hodnocení",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "Vyloučit",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Experimentální",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "Exportovat",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "Externí modely",
|
||||
"Failed to add file.": "Nepodařilo se přidat soubor.",
|
||||
"Failed to create API Key.": "Nepodařilo se vytvořit API klíč.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Nepodařilo se přečíst obsah schránky",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "Nepodařilo se aktualizovat nastavení",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "Formátujte své proměnné pomocí závorek takto:",
|
||||
"Frequency Penalty": "Penalizace frekvence",
|
||||
"Function": "Funkce",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "Funkce byla úspěšně vytvořena.",
|
||||
"Function deleted successfully": "Funkce byla úspěšně odstraněna",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "Funkce byly úspěšně importovány",
|
||||
"General": "Obecný",
|
||||
"General Settings": "Obecná nastavení",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "Vygenerovat obrázek",
|
||||
"Generating search query": "Generování vyhledávacího dotazu",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "hh:mm dop./odp.",
|
||||
"Haptic Feedback": "Haptická zpětná vazba",
|
||||
"has no conversations.": "nemá žádné konverzace.",
|
||||
"Hello, {{name}}": "Ahoj, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Generování obrázků (experimentální)",
|
||||
"Image Generation Engine": "Engine pro generování obrázků",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, RRRR",
|
||||
"MMMM DD, YYYY HH:mm": "MMMM DD, RRRR HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
|
||||
"Model": "Model",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model „{{modelName}}“ byl úspěšně stažen.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je již zařazen do fronty pro stahování.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "Více",
|
||||
"Name": "Jméno",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Nový chat",
|
||||
"New Folder": "",
|
||||
"New Password": "Nové heslo",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "Prosím, zadejte zadání.",
|
||||
"Please fill in all fields.": "Prosím, vyplňte všechna pole.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "Prosím vyberte důvod",
|
||||
"Port": "",
|
||||
"Positive attitude": "Pozitivní přístup",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "Předchozích 30 dnů",
|
||||
"Previous 7 days": "Předchozích 7 dní",
|
||||
"Profile Image": "Profilový obrázek",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (např. Řekni mi zábavný fakt o Římské říši)",
|
||||
"Prompt Content": "Obsah promptu",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "Znovu seřaďte modely podle podobnosti témat.",
|
||||
"Read": "",
|
||||
"Read Aloud": "Číst nahlas",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Nahrát hlas",
|
||||
"Redirecting you to OpenWebUI Community": "Přesměrování na komunitu OpenWebUI",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "Vyhledávací dotazy",
|
||||
"Search Result Count": "Počet výsledků hledání",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "Nástroje pro vyhledávání",
|
||||
"SearchApi API Key": "Klíč API pro SearchApi",
|
||||
"SearchApi Engine": "Vyhledávací engine API",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Toto obnoví znalostní databázi a synchronizuje všechny soubory. Přejete si pokračovat?",
|
||||
"Thorough explanation": "Obsáhlé vysvětlení",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "Tika",
|
||||
"Tika Server URL required.": "Je vyžadována URL adresa serveru Tika.",
|
||||
"Tiktoken": "Tiktoken",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "Nástroje",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "Nástroje jsou systémem pro volání funkcí s vykonáváním libovolného kódu.",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Nástroje mají systém volání funkcí, který umožňuje libovolné spouštění kódu.",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Nástroje mají systém volání funkcí, který umožňuje spuštění libovolného kódu.",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Nastavení Web Loaderu",
|
||||
"Web Search": "Vyhledávání na webu",
|
||||
"Web Search Engine": "Webový vyhledávač",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"WebUI Settings": "Nastavení WebUI",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Můžete personalizovat své interakce s LLM pomocí přidávání vzpomínek prostřednictvím tlačítka 'Spravovat' níže, což je učiní pro vás užitečnějšími a lépe přizpůsobenými.",
|
||||
"You cannot upload an empty file.": "Nemůžete nahrát prázdný soubor.",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "Nemáte žádné archivované konverzace.",
|
||||
"You have shared this chat": "Sdíleli jste tento chat.",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Har du allerede en profil?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "en assistent",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "og",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "og lav et nyt link til deling",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Skriveadgang til udklipsholderen ikke tilladt. Tjek venligst indstillingerne i din browser for at give adgang.",
|
||||
"Clone": "Klon",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Luk",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "Kode formateret korrekt",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Samling",
|
||||
"Color": "",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "Bekræft din handling",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Forbindelser",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "Kontakt din administrator for adgang til WebUI",
|
||||
"Content": "Indhold",
|
||||
"Content Extraction": "Udtræk af indhold",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Standard",
|
||||
"Default (Open AI)": "Standard (Open AI)",
|
||||
"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "Standard model",
|
||||
"Default model updated": "Standard model opdateret",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Indtast overlapning af tekststykker",
|
||||
"Enter Chunk Size": "Indtast størrelse af tekststykker",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "Indtast Github Raw URL",
|
||||
"Enter Google PSE API Key": "Indtast Google PSE API-nøgle",
|
||||
"Enter Google PSE Engine Id": "Indtast Google PSE Engine ID",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Indtast antal trin (f.eks. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "Indtast sampler (f.eks. Euler a)",
|
||||
"Enter Scheduler (e.g. Karras)": "Indtast scheduler (f.eks. Karras)",
|
||||
"Enter Score": "Indtast score",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Eksperimentel",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "Eksportér",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "Eksterne modeller",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "Kunne ikke oprette API-nøgle.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Kunne ikke læse indholdet af udklipsholderen",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "Kunne ikke opdatere indstillinger",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "Hyppighedsstraf",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "Funktion oprettet.",
|
||||
"Function deleted successfully": "Funktion slettet.",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "Funktioner importeret.",
|
||||
"General": "Generelt",
|
||||
"General Settings": "Generelle indstillinger",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "Generer billede",
|
||||
"Generating search query": "Genererer søgeforespørgsel",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "h:mm a",
|
||||
"Haptic Feedback": "Haptisk feedback",
|
||||
"has no conversations.": "har ingen samtaler.",
|
||||
"Hello, {{name}}": "Hej {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Billedgenerering (eksperimentel)",
|
||||
"Image Generation Engine": "Billedgenereringsengine",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' er blevet downloadet.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' er allerede i kø til download.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "Mere",
|
||||
"Name": "Navn",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "Ny chat",
|
||||
"New Folder": "",
|
||||
"New Password": "Ny adgangskode",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "Udfyld alle felter.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "Vælg en årsag",
|
||||
"Port": "",
|
||||
"Positive attitude": "Positiv holdning",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "Seneste 30 dage",
|
||||
"Previous 7 days": "Seneste 7 dage",
|
||||
"Profile Image": "Profilbillede",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (f.eks. Fortæl mig en sjov kendsgerning om Romerriget)",
|
||||
"Prompt Content": "Promptindhold",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "Læs højt",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Optag stemme",
|
||||
"Redirecting you to OpenWebUI Community": "Omdirigerer dig til OpenWebUI Community",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "Søg i prompts",
|
||||
"Search Result Count": "Antal søgeresultater",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "Søg i værktøjer",
|
||||
"SearchApi API Key": "SearchApi API-nøgle",
|
||||
"SearchApi Engine": "SearchApi-engine",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dette vil nulstille vidensbasen og synkronisere alle filer. Vil du fortsætte?",
|
||||
"Thorough explanation": "Grundig forklaring",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "Tika",
|
||||
"Tika Server URL required.": "Tika-server-URL påkrævet.",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "Værktøjer",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "Værktøjer er et funktionkaldssystem med vilkårlig kodeudførelse",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Værktøjer har et funktionkaldssystem, der tillader vilkårlig kodeudførelse.",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Web Loader-indstillinger",
|
||||
"Web Search": "Websøgning",
|
||||
"Web Search Engine": "Websøgemaskine",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "Webhook-URL",
|
||||
"WebUI Settings": "WebUI-indstillinger",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Du kan personliggøre dine interaktioner med LLM'er ved at tilføje minder via knappen 'Administrer' nedenfor, hvilket gør dem mere nyttige og skræddersyet til dig.",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "Du har ingen arkiverede samtaler.",
|
||||
"You have shared this chat": "Du har delt denne chat",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Haben Sie bereits einen Account?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "Alternative zu top_p und zielt darauf ab, ein Gleichgewicht zwischen Qualität und Vielfalt zu gewährleisten. Der Parameter p repräsentiert die Mindestwahrscheinlichkeit für ein Token, um berücksichtigt zu werden, relativ zur Wahrscheinlichkeit des wahrscheinlichsten Tokens. Zum Beispiel, bei p=0.05 und das wahrscheinlichste Token hat eine Wahrscheinlichkeit von 0.9, werden Logits mit einem Wert von weniger als 0.045 herausgefiltert. (Standard: 0.0)",
|
||||
"Always": "",
|
||||
"Amazing": "Fantastisch",
|
||||
"an assistant": "ein Assistent",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "und",
|
||||
"and {{COUNT}} more": "und {{COUNT}} mehr",
|
||||
"and create a new shared link.": "und erstellen Sie einen neuen freigegebenen Link.",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Schreibberechtigung für die Zwischenablage verweigert. Bitte überprüfen Sie Ihre Browsereinstellungen, um den erforderlichen Zugriff zu erlauben.",
|
||||
"Clone": "Klonen",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Schließen",
|
||||
"Code execution": "Codeausführung",
|
||||
"Code formatted successfully": "Code erfolgreich formatiert",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Kollektion",
|
||||
"Color": "Farbe",
|
||||
"ComfyUI": "ComfyUI",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "Bestätigen Sie Ihre Aktion.",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Verbindungen",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche",
|
||||
"Content": "Info",
|
||||
"Content Extraction": "Inhaltsextraktion",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Standard",
|
||||
"Default (Open AI)": "Standard (Open AI)",
|
||||
"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "Standardmodell",
|
||||
"Default model updated": "Standardmodell aktualisiert",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Geben Sie die Blocküberlappung ein",
|
||||
"Enter Chunk Size": "Geben Sie die Blockgröße ein",
|
||||
"Enter description": "Geben Sie eine Beschreibung ein",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "Geben Sie die Github Raw-URL ein",
|
||||
"Enter Google PSE API Key": "Geben Sie den Google PSE-API-Schlüssel ein",
|
||||
"Enter Google PSE Engine Id": "Geben Sie die Google PSE-Engine-ID ein",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "Geben Sie den Mojeek Search API-Schlüssel ein",
|
||||
"Enter Number of Steps (e.g. 50)": "Geben Sie die Anzahl an Schritten ein (z. B. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "Geben Sie den Sampler ein (z. B. Euler a)",
|
||||
"Enter Scheduler (e.g. Karras)": "Geben Sie den Scheduler ein (z. B. Karras)",
|
||||
"Enter Score": "Punktzahl eingeben",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "Evaluationen",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "Beispiel: (&(objectClass=inetOrgPerson)(uid=%s))",
|
||||
"Example: ALL": "Beispiel: ALL",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "Beispiel: ou=users,dc=foo,dc=example",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "Beispiel: sAMAccountName or uid or userPrincipalName",
|
||||
"Exclude": "Ausschließen",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Experimentell",
|
||||
"Explore the cosmos": "Erforschen Sie das Universum",
|
||||
"Export": "Exportieren",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "Externe Modelle",
|
||||
"Failed to add file.": "Fehler beim Hinzufügen der Datei.",
|
||||
"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "Formatieren Sie Ihre Variablen mit Klammern, wie hier:",
|
||||
"Frequency Penalty": "Frequenzstrafe",
|
||||
"Function": "Funktion",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "Funktion erfolgreich erstellt",
|
||||
"Function deleted successfully": "Funktion erfolgreich gelöscht",
|
||||
"Function Description": "Funktionsbeschreibung",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "Funktionen erfolgreich importiert",
|
||||
"General": "Allgemein",
|
||||
"General Settings": "Allgemeine Einstellungen",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "Bild erzeugen",
|
||||
"Generating search query": "Suchanfrage wird erstellt",
|
||||
"Get started": "Loslegen",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "Gruppenname",
|
||||
"Group updated successfully": "Gruppe erfolgreich aktualisiert",
|
||||
"Groups": "Gruppen",
|
||||
"h:mm a": "h:mm a",
|
||||
"Haptic Feedback": "Haptisches Feedback",
|
||||
"has no conversations.": "hat keine Unterhaltungen.",
|
||||
"Hello, {{name}}": "Hallo, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "Neugier entfachen",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Bildgenerierung (experimentell)",
|
||||
"Image Generation Engine": "Bildgenerierungs-Engine",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "DD MMMM YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "DD MMMM YYYY HH:mm",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "DD MMMM YYYY HH:mm A",
|
||||
"Model": "Modell",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange zum Herunterladen.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "Mehr",
|
||||
"Name": "Name",
|
||||
"Name your knowledge base": "Benennen Sie Ihren Wissensspeicher",
|
||||
"Native": "",
|
||||
"New Chat": "Neue Unterhaltung",
|
||||
"New Folder": "",
|
||||
"New Password": "Neues Passwort",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "Bitte geben Sie einen Prompt ein",
|
||||
"Please fill in all fields.": "Bitte füllen Sie alle Felder aus.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "Bitte wählen Sie einen Grund aus",
|
||||
"Port": "Port",
|
||||
"Positive attitude": "Positive Einstellung",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "Vorherige 30 Tage",
|
||||
"Previous 7 days": "Vorherige 7 Tage",
|
||||
"Profile Image": "Profilbild",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (z. B. \"Erzähle mir eine interessante Tatsache über das Römische Reich\")",
|
||||
"Prompt Content": "Prompt-Inhalt",
|
||||
"Prompt created successfully": "Prompt erfolgreich erstellt",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "Modelle nach thematischer Ähnlichkeit neu ordnen",
|
||||
"Read": "",
|
||||
"Read Aloud": "Vorlesen",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Stimme aufnehmen",
|
||||
"Redirecting you to OpenWebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "Reduziert die Wahrscheinlichkeit, Unsinn zu generieren. Ein höherer Wert (z.B. 100) liefert vielfältigere Antworten, während ein niedrigerer Wert (z.B. 10) konservativer ist. (Standard: 40)",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "Suchoptionen",
|
||||
"Search Prompts": "Prompts durchsuchen...",
|
||||
"Search Result Count": "Anzahl der Suchergebnisse",
|
||||
"Search the web": "Im Web suchen",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "Werkzeuge durchsuchen...",
|
||||
"SearchApi API Key": "SearchApi-API-Schlüssel",
|
||||
"SearchApi Engine": "SearchApi-Engine",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "Dies wird alle Modelle einschließlich benutzerdefinierter Modelle löschen und kann nicht rückgängig gemacht werden.",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "Dadurch wird die Wissensdatenbank zurückgesetzt und alle Dateien synchronisiert. Möchten Sie fortfahren?",
|
||||
"Thorough explanation": "Ausführliche Erklärung",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "Tika",
|
||||
"Tika Server URL required.": "Tika-Server-URL erforderlich.",
|
||||
"Tiktoken": "Tiktoken",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "Werkzeuge",
|
||||
"Tools Access": "Werkzeugzugriff",
|
||||
"Tools are a function calling system with arbitrary code execution": "Wekzeuge sind ein Funktionssystem mit beliebiger Codeausführung",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "Werkezuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "Werkzeuge verfügen über ein Funktionssystem, das die Ausführung beliebigen Codes ermöglicht.",
|
||||
"Top K": "Top K",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "Web Loader Einstellungen",
|
||||
"Web Search": "Websuche",
|
||||
"Web Search Engine": "Suchmaschine",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"WebUI Settings": "WebUI-Einstellungen",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.",
|
||||
"You cannot upload an empty file.": "Sie können keine leere Datei hochladen.",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "Sie haben keine Berechtigung zum Hochladen von Dateien.",
|
||||
"You have no archived conversations.": "Du hast keine archivierten Unterhaltungen.",
|
||||
"You have shared this chat": "Sie haben diese Unterhaltung geteilt",
|
||||
|
||||
@@ -63,8 +63,11 @@
|
||||
"Allowed Endpoints": "",
|
||||
"Already have an account?": "Such account exists?",
|
||||
"Alternative to the top_p, and aims to ensure a balance of quality and variety. The parameter p represents the minimum probability for a token to be considered, relative to the probability of the most likely token. For example, with p=0.05 and the most likely token having a probability of 0.9, logits with a value less than 0.045 are filtered out. (Default: 0.0)": "",
|
||||
"Always": "",
|
||||
"Amazing": "",
|
||||
"an assistant": "such assistant",
|
||||
"Analyzed": "",
|
||||
"Analyzing...": "",
|
||||
"and": "and",
|
||||
"and {{COUNT}} more": "",
|
||||
"and create a new shared link.": "",
|
||||
@@ -169,9 +172,11 @@
|
||||
"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "",
|
||||
"Clone": "",
|
||||
"Clone Chat": "",
|
||||
"Clone of {{TITLE}}": "",
|
||||
"Close": "Close",
|
||||
"Code execution": "",
|
||||
"Code formatted successfully": "",
|
||||
"Code Interpreter": "",
|
||||
"Collection": "Collection",
|
||||
"Color": "",
|
||||
"ComfyUI": "",
|
||||
@@ -189,6 +194,7 @@
|
||||
"Confirm your action": "",
|
||||
"Confirm your new password": "",
|
||||
"Connections": "Connections",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "Content",
|
||||
"Content Extraction": "",
|
||||
@@ -232,6 +238,7 @@
|
||||
"Default": "Default",
|
||||
"Default (Open AI)": "",
|
||||
"Default (SentenceTransformers)": "",
|
||||
"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
|
||||
"Default Model": "",
|
||||
"Default model updated": "Default model much updated",
|
||||
"Default Models": "",
|
||||
@@ -342,6 +349,7 @@
|
||||
"Enter Chunk Overlap": "Enter Overlap of Chunks",
|
||||
"Enter Chunk Size": "Enter Size of Chunk",
|
||||
"Enter description": "",
|
||||
"Enter Exa API Key": "",
|
||||
"Enter Github Raw URL": "",
|
||||
"Enter Google PSE API Key": "",
|
||||
"Enter Google PSE Engine Id": "",
|
||||
@@ -354,6 +362,7 @@
|
||||
"Enter Mojeek Search API Key": "",
|
||||
"Enter Number of Steps (e.g. 50)": "Enter Number of Steps (e.g. 50)",
|
||||
"Enter proxy URL (e.g. https://user:password@host:port)": "",
|
||||
"Enter reasoning effort": "",
|
||||
"Enter Sampler (e.g. Euler a)": "",
|
||||
"Enter Scheduler (e.g. Karras)": "",
|
||||
"Enter Score": "",
|
||||
@@ -389,12 +398,14 @@
|
||||
"Error accessing Google Drive: {{error}}": "",
|
||||
"Error uploading file: {{error}}": "",
|
||||
"Evaluations": "",
|
||||
"Exa API Key": "",
|
||||
"Example: (&(objectClass=inetOrgPerson)(uid=%s))": "",
|
||||
"Example: ALL": "",
|
||||
"Example: mail": "",
|
||||
"Example: ou=users,dc=foo,dc=example": "",
|
||||
"Example: sAMAccountName or uid or userPrincipalName": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Experimental": "Much Experiment",
|
||||
"Explore the cosmos": "",
|
||||
"Export": "",
|
||||
@@ -412,6 +423,7 @@
|
||||
"External Models": "",
|
||||
"Failed to add file.": "",
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Failed to read clipboard borks",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
@@ -446,6 +458,7 @@
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Frequency Penalty": "",
|
||||
"Function": "",
|
||||
"Function Calling": "",
|
||||
"Function created successfully": "",
|
||||
"Function deleted successfully": "",
|
||||
"Function Description": "",
|
||||
@@ -460,6 +473,7 @@
|
||||
"Functions imported successfully": "",
|
||||
"General": "Woweral",
|
||||
"General Settings": "General Doge Settings",
|
||||
"Generate an image": "",
|
||||
"Generate Image": "",
|
||||
"Generating search query": "",
|
||||
"Get started": "",
|
||||
@@ -475,7 +489,6 @@
|
||||
"Group Name": "",
|
||||
"Group updated successfully": "",
|
||||
"Groups": "",
|
||||
"h:mm a": "",
|
||||
"Haptic Feedback": "",
|
||||
"has no conversations.": "",
|
||||
"Hello, {{name}}": "Much helo, {{name}}",
|
||||
@@ -493,7 +506,6 @@
|
||||
"Ignite curiosity": "",
|
||||
"Image": "",
|
||||
"Image Compression": "",
|
||||
"Image generation": "",
|
||||
"Image Generation": "",
|
||||
"Image Generation (Experimental)": "Image Wow (Much Experiment)",
|
||||
"Image Generation Engine": "Image Engine",
|
||||
@@ -592,9 +604,6 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"MMMM DD, YYYY": "MMMM DD, YYYY",
|
||||
"MMMM DD, YYYY HH:mm": "",
|
||||
"MMMM DD, YYYY hh:mm:ss A": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.",
|
||||
@@ -621,6 +630,7 @@
|
||||
"More": "",
|
||||
"Name": "Name",
|
||||
"Name your knowledge base": "",
|
||||
"Native": "",
|
||||
"New Chat": "New Bark",
|
||||
"New Folder": "",
|
||||
"New Password": "New Barkword",
|
||||
@@ -715,6 +725,7 @@
|
||||
"Please enter a prompt": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
"Please select a reason": "",
|
||||
"Port": "",
|
||||
"Positive attitude": "",
|
||||
@@ -723,6 +734,7 @@
|
||||
"Previous 30 days": "",
|
||||
"Previous 7 days": "",
|
||||
"Profile Image": "",
|
||||
"Prompt": "",
|
||||
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
|
||||
"Prompt Content": "Prompt Content",
|
||||
"Prompt created successfully": "",
|
||||
@@ -740,6 +752,7 @@
|
||||
"Re-rank models by topic similarity": "",
|
||||
"Read": "",
|
||||
"Read Aloud": "",
|
||||
"Reasoning Effort": "",
|
||||
"Record voice": "Record Bark",
|
||||
"Redirecting you to OpenWebUI Community": "Redirecting you to OpenWebUI Community",
|
||||
"Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40)": "",
|
||||
@@ -797,7 +810,7 @@
|
||||
"Search options": "",
|
||||
"Search Prompts": "Search Prompts much wow",
|
||||
"Search Result Count": "",
|
||||
"Search the web": "",
|
||||
"Search the internet": "",
|
||||
"Search Tools": "",
|
||||
"SearchApi API Key": "",
|
||||
"SearchApi Engine": "",
|
||||
@@ -930,6 +943,7 @@
|
||||
"This will delete all models including custom models and cannot be undone.": "",
|
||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "",
|
||||
"Thorough explanation": "",
|
||||
"Thought for {{DURATION}}": "",
|
||||
"Tika": "",
|
||||
"Tika Server URL required.": "",
|
||||
"Tiktoken": "",
|
||||
@@ -966,6 +980,7 @@
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
"Tools are a function calling system with arbitrary code execution": "",
|
||||
"Tools Function Calling Prompt": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution": "",
|
||||
"Tools have a function calling system that allows arbitrary code execution.": "",
|
||||
"Top K": "Top K very top",
|
||||
@@ -1036,6 +1051,7 @@
|
||||
"Web Loader Settings": "",
|
||||
"Web Search": "",
|
||||
"Web Search Engine": "",
|
||||
"Web Search in Chat": "",
|
||||
"Web Search Query Generation": "",
|
||||
"Webhook URL": "",
|
||||
"WebUI Settings": "WebUI Settings much settings",
|
||||
@@ -1065,6 +1081,7 @@
|
||||
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
|
||||
"You cannot upload an empty file.": "",
|
||||
"You do not have permission to access this feature.": "",
|
||||
"You do not have permission to upload files": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You have no archived conversations.": "",
|
||||
"You have shared this chat": "",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user