Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63533c9e3a | ||
|
|
8e8bc3125e | ||
|
|
2c083f58d9 | ||
|
|
93962f01cc | ||
|
|
40d019ff50 | ||
|
|
3885ea4a15 | ||
|
|
bd28173de6 | ||
|
|
06820e701c | ||
|
|
2be08f27ea | ||
|
|
fabe5876c0 | ||
|
|
f1a4afeb8f | ||
|
|
914eb49767 | ||
|
|
a8bc0d65a0 | ||
|
|
cbe2056587 | ||
|
|
65ed76abe1 | ||
|
|
bc17511a05 | ||
|
|
3413dd8ae1 | ||
|
|
03759e79a3 | ||
|
|
20211a489f | ||
|
|
5d2e725f81 | ||
|
|
2722613cde | ||
|
|
f243e523a6 | ||
|
|
155dbd5a66 | ||
|
|
9825d03602 | ||
|
|
66cd40f959 | ||
|
|
dc2f8ec8c6 | ||
|
|
bf118718b3 | ||
|
|
64a0b28dab | ||
|
|
6751d68034 | ||
|
|
6274f4d0d3 | ||
|
|
624c5250b1 | ||
|
|
c1ff69721a | ||
|
|
1e98ae7608 | ||
|
|
1bde36e072 | ||
|
|
89e7913ff2 | ||
|
|
51bf9eaa88 | ||
|
|
b8cf9b738d | ||
|
|
635c08a5ab | ||
|
|
6323b9f4f0 | ||
|
|
94453839ba | ||
|
|
a506a1a61e | ||
|
|
d266490862 | ||
|
|
ff1d454bdd | ||
|
|
eda3eba084 | ||
|
|
60c6e00d9f | ||
|
|
246c839ee6 | ||
|
|
2994c58fd9 | ||
|
|
4476060044 | ||
|
|
2729d8aab4 | ||
|
|
26e1bfc772 | ||
|
|
9ea6cea0ba | ||
|
|
71625663c0 | ||
|
|
33451cf66d | ||
|
|
41aca84913 | ||
|
|
5296ee0f71 | ||
|
|
da94835165 | ||
|
|
13dfca4e76 | ||
|
|
d5063f47f2 | ||
|
|
ef787e4a79 | ||
|
|
48d690c55d | ||
|
|
69c68df34b | ||
|
|
b3110ca291 | ||
|
|
cd0a1b4852 | ||
|
|
32f309b41d | ||
|
|
d23f757c1f | ||
|
|
324550423c | ||
|
|
b4277c7d2e | ||
|
|
84788b73ce | ||
|
|
09344bbb80 | ||
|
|
fe4f760b4a | ||
|
|
807b208418 | ||
|
|
ae484e8edf | ||
|
|
56dc7c5640 | ||
|
|
e570a98bf7 | ||
|
|
93bb77ede3 | ||
|
|
66db2e1515 | ||
|
|
c9e9ce931b | ||
|
|
9747a0e1f1 | ||
|
|
61778c8f71 | ||
|
|
579aca65bb | ||
|
|
ee44383c83 | ||
|
|
0c0505e1cd | ||
|
|
1b8557ce86 | ||
|
|
e0da6003f1 | ||
|
|
0f310b3509 | ||
|
|
8cf8121812 | ||
|
|
a2f2203325 | ||
|
|
193a927aba | ||
|
|
4ad10f0c6e | ||
|
|
5e06b6d3f4 | ||
|
|
cb94a87cb0 | ||
|
|
793aa307ef | ||
|
|
7e46cc4562 | ||
|
|
b612af29e9 | ||
|
|
feaf434d4e | ||
|
|
77fa11098a | ||
|
|
a32bb850fa | ||
|
|
64b68b3641 | ||
|
|
3b2b6e183d | ||
|
|
138e985d66 | ||
|
|
ec3435de00 | ||
|
|
a25d87607e | ||
|
|
1c57e3e02c | ||
|
|
41b9ff9e00 | ||
|
|
6d5cb6b04e | ||
|
|
1366822f99 | ||
|
|
2b0d9c26eb | ||
|
|
be20e6dec0 | ||
|
|
a1f3300767 | ||
|
|
dfdde8bb1f | ||
|
|
ba77a72925 | ||
|
|
bcf0a8791e | ||
|
|
100f5a57e7 | ||
|
|
911321878f | ||
|
|
faa68fcdaa | ||
|
|
561b2c0b1a | ||
|
|
436e3ff7de | ||
|
|
c0711ba0c9 | ||
|
|
5c5160c294 | ||
|
|
2277566ce1 | ||
|
|
959995c715 | ||
|
|
7a1e10f3a7 | ||
|
|
b15bf0dc70 | ||
|
|
506950b03a | ||
|
|
94353458a5 | ||
|
|
7eea95a48c | ||
|
|
94bf49440d | ||
|
|
0644abe402 | ||
|
|
9036945361 | ||
|
|
dd5bafe8af | ||
|
|
548c7f17d7 | ||
|
|
bb7f4d408a | ||
|
|
6ee30042e7 | ||
|
|
0554bbb1cb | ||
|
|
517a57b7ca | ||
|
|
ee68c9ea55 | ||
|
|
d65471c420 | ||
|
|
de8f94b008 | ||
|
|
0ac00b9256 | ||
|
|
d0db4756a6 | ||
|
|
adaa614986 | ||
|
|
5ab783001d | ||
|
|
2a163996f9 | ||
|
|
1b37bdcec8 | ||
|
|
39b0c06630 | ||
|
|
2b7dd6e399 | ||
|
|
c5a8d2f857 | ||
|
|
93d7702e8c | ||
|
|
0447d90951 | ||
|
|
8f8c34471e | ||
|
|
883ad55f5c | ||
|
|
82e5a6410c | ||
|
|
88b93241db | ||
|
|
b652b8e5f3 | ||
|
|
1ac6879268 | ||
|
|
825becceb0 | ||
|
|
fffa793d6d | ||
|
|
289d49dbe7 | ||
|
|
fa72c277db | ||
|
|
8799ff9575 | ||
|
|
85c8a9bd68 | ||
|
|
15f6e3af12 | ||
|
|
c79fa4214f | ||
|
|
29d5745335 | ||
|
|
70bb0562f3 | ||
|
|
c8210d4043 | ||
|
|
9f470a4ff5 | ||
|
|
cea5c1a63b | ||
|
|
4ce05ad279 | ||
|
|
f3c2614b6c | ||
|
|
79cca06e0c | ||
|
|
b60beb6ff6 | ||
|
|
295c7eb4c7 | ||
|
|
361584b4fd | ||
|
|
abba7c128c | ||
|
|
e0ec2cdeb0 | ||
|
|
bb0e11bb97 | ||
|
|
fc5c8dbe3f | ||
|
|
1d30d8a13a |
@@ -1,12 +1,26 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: uv
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: monthly
|
||||
target-branch: 'dev'
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: '/backend'
|
||||
schedule:
|
||||
interval: monthly
|
||||
target-branch: 'dev'
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: monthly
|
||||
target-branch: 'dev'
|
||||
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: monthly
|
||||
target-branch: 'dev'
|
||||
|
||||
@@ -5,10 +5,18 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,7 +25,9 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.11]
|
||||
python-version:
|
||||
- 3.11.x
|
||||
- 3.12.x
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -25,7 +35,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: '${{ matrix.python-version }}'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
@@ -5,10 +5,18 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths-ignore:
|
||||
- 'backend/**'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths-ignore:
|
||||
- 'backend/**'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -21,7 +29,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22' # Or specify any other version you want to use
|
||||
node-version: '22'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
@@ -5,6 +5,46 @@ 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.6.2] - 2025-04-06
|
||||
|
||||
### Added
|
||||
|
||||
- 🌍 **Improved Global Language Support**: Expanded and refined translations across multiple languages to enhance clarity and consistency for international users.
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🛠️ **Accurate Tool Descriptions from OpenAPI Servers**: External tools now use full endpoint descriptions instead of summaries when generating tool specifications—helping AI models understand tool purpose more precisely and choose the right tool more accurately in tool workflows.
|
||||
- 🔧 **Precise Web Results Source Attribution**: Fixed a key issue where all web search results showed the same source ID—now each result gets its correct and distinct source, ensuring accurate citations and traceability.
|
||||
- 🔍 **Clean Web Search Retrieval**: Web search now retains only results from URLs where real content was successfully fetched—improving accuracy and removing empty or broken links from citations.
|
||||
- 🎵 **Audio File Upload Response Restored**: Resolved an issue where uploading audio files did not return valid responses, restoring smooth file handling for transcription and audio-based workflows.
|
||||
|
||||
### Changed
|
||||
|
||||
- 🧰 **General Backend Refactoring**: Multiple behind-the-scenes improvements streamline backend performance, reduce complexity, and ensure a more stable, maintainable system overall—making everything smoother without changing your workflow.
|
||||
|
||||
## [0.6.1] - 2025-04-05
|
||||
|
||||
### Added
|
||||
|
||||
- 🛠️ **Global Tool Servers Configuration**: Admins can now centrally configure global external tool servers from Admin Settings > Tools, allowing seamless sharing of tool integrations across all users without manual setup per user.
|
||||
- 🔐 **Direct Tool Usage Permission for Users**: Introduced a new user-level permission toggle that grants non-admin users access to direct external tools, empowering broader team collaboration while maintaining control.
|
||||
- 🧠 **Mistral OCR Content Extraction Support**: Added native support for Mistral OCR as a high-accuracy document loader, drastically improving text extraction from scanned documents in RAG workflows.
|
||||
- 🖼️ **Tools Indicator UI Redesign**: Enhanced message input now smartly displays both built-in and external tools via a unified dropdown, making it simpler and more intuitive to activate tools during conversations.
|
||||
- 📄 **RAG Prompt Improved and More Coherent**: Default RAG system prompt has been revised to be more clear and citation-focused—admins can leave the template field empty to use this new gold-standard prompt.
|
||||
- 🧰 **Performance & Developer Improvements**: Major internal restructuring of several tool-related components, simplifying styling and merging external/internal handling logic, resulting in better maintainability and performance.
|
||||
- 🌍 **Improved Translations**: Updated translations for Tibetan, Polish, Chinese (Simplified & Traditional), Arabic, Russian, Ukrainian, Dutch, Finnish, and French to improve clarity and consistency across the interface.
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🔑 **External Tool Server API Key Bug Resolved**: Fixed a critical issue where authentication headers were not being sent when calling tools from external OpenAPI tool servers, ensuring full security and smooth tool operations.
|
||||
- 🚫 **Conditional Export Button Visibility**: UI now gracefully hides export buttons when there's nothing to export in models, prompts, tools, or functions, improving visual clarity and reducing confusion.
|
||||
- 🧪 **Hybrid Search Failure Recovery**: Resolved edge case in parallel hybrid search where empty or unindexed collections caused backend crashes—these are now cleanly skipped to ensure system stability.
|
||||
- 📂 **Admin Folder Deletion Fix**: Addressed an issue where folders created in the admin workspace couldn't be deleted, restoring full organizational flexibility for admins.
|
||||
- 🔐 **Improved Generic Error Feedback on Login**: Authentication errors now show simplified, non-revealing messages for privacy and improved UX, especially with federated logins.
|
||||
- 📝 **Tool Message with Images Improved**: Enhanced how tool-generated messages with image outputs are shown in chat, making them more readable and consistent with the overall UI design.
|
||||
- ⚙️ **Auto-Exclusion for Broken RAG Collections**: Auto-skips document collections that fail to fetch data or return "None", preventing silent errors and streamlining retrieval workflows.
|
||||
- 📝 **Docling Text File Handling Fix**: Fixed file parsing inconsistency that broke docling-based RAG functionality for certain plain text files, ensuring wider file compatibility.
|
||||
|
||||
## [0.6.0] - 2025-03-31
|
||||
|
||||
### Added
|
||||
|
||||
@@ -331,12 +331,14 @@ JWT_EXPIRES_IN = PersistentConfig(
|
||||
# OAuth config
|
||||
####################################
|
||||
|
||||
|
||||
ENABLE_OAUTH_SIGNUP = PersistentConfig(
|
||||
"ENABLE_OAUTH_SIGNUP",
|
||||
"oauth.enable_signup",
|
||||
os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true",
|
||||
)
|
||||
|
||||
|
||||
OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig(
|
||||
"OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
|
||||
"oauth.merge_accounts_by_email",
|
||||
@@ -466,6 +468,7 @@ OAUTH_USERNAME_CLAIM = PersistentConfig(
|
||||
os.environ.get("OAUTH_USERNAME_CLAIM", "name"),
|
||||
)
|
||||
|
||||
|
||||
OAUTH_PICTURE_CLAIM = PersistentConfig(
|
||||
"OAUTH_PICTURE_CLAIM",
|
||||
"oauth.oidc.avatar_claim",
|
||||
@@ -878,6 +881,17 @@ except Exception:
|
||||
pass
|
||||
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
|
||||
|
||||
####################################
|
||||
# TOOL_SERVERS
|
||||
####################################
|
||||
|
||||
|
||||
TOOL_SERVER_CONNECTIONS = PersistentConfig(
|
||||
"TOOL_SERVER_CONNECTIONS",
|
||||
"tool_server.connections",
|
||||
[],
|
||||
)
|
||||
|
||||
####################################
|
||||
# WEBUI
|
||||
####################################
|
||||
@@ -1034,6 +1048,11 @@ USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = (
|
||||
== "true"
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS = (
|
||||
os.environ.get("USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS", "False").lower()
|
||||
== "true"
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_FEATURES_WEB_SEARCH = (
|
||||
os.environ.get("USER_PERMISSIONS_FEATURES_WEB_SEARCH", "True").lower() == "true"
|
||||
)
|
||||
@@ -1071,6 +1090,7 @@ DEFAULT_USER_PERMISSIONS = {
|
||||
"temporary_enforced": USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
|
||||
},
|
||||
"features": {
|
||||
"direct_tool_servers": USER_PERMISSIONS_FEATURES_DIRECT_TOOL_SERVERS,
|
||||
"web_search": USER_PERMISSIONS_FEATURES_WEB_SEARCH,
|
||||
"image_generation": USER_PERMISSIONS_FEATURES_IMAGE_GENERATION,
|
||||
"code_interpreter": USER_PERMISSIONS_FEATURES_CODE_INTERPRETER,
|
||||
@@ -1727,6 +1747,11 @@ DOCUMENT_INTELLIGENCE_KEY = PersistentConfig(
|
||||
os.getenv("DOCUMENT_INTELLIGENCE_KEY", ""),
|
||||
)
|
||||
|
||||
MISTRAL_OCR_API_KEY = PersistentConfig(
|
||||
"MISTRAL_OCR_API_KEY",
|
||||
"rag.mistral_ocr_api_key",
|
||||
os.getenv("MISTRAL_OCR_API_KEY", ""),
|
||||
)
|
||||
|
||||
BYPASS_EMBEDDING_AND_RETRIEVAL = PersistentConfig(
|
||||
"BYPASS_EMBEDDING_AND_RETRIEVAL",
|
||||
@@ -1875,7 +1900,7 @@ CHUNK_OVERLAP = PersistentConfig(
|
||||
)
|
||||
|
||||
DEFAULT_RAG_TEMPLATE = """### Task:
|
||||
Respond to the user query using the provided context, incorporating inline citations in the format [source_id] **only when the <source_id> tag is explicitly provided** in the context.
|
||||
Respond to the user query using the provided context, incorporating inline citations in the format [id] **only when the <source> tag includes an explicit id attribute** (e.g., <source id="1">).
|
||||
|
||||
### Guidelines:
|
||||
- If you don't know the answer, clearly state that.
|
||||
@@ -1883,18 +1908,17 @@ Respond to the user query using the provided context, incorporating inline citat
|
||||
- Respond in the same language as the user's query.
|
||||
- If the context is unreadable or of poor quality, inform the user and provide the best possible answer.
|
||||
- If the answer isn't present in the context but you possess the knowledge, explain this to the user and provide the answer using your own understanding.
|
||||
- **Only include inline citations using [source_id] (e.g., [1], [2]) when a `<source_id>` tag is explicitly provided in the context.**
|
||||
- Do not cite if the <source_id> tag is not provided in the context.
|
||||
- **Only include inline citations using [id] (e.g., [1], [2]) when the <source> tag includes an id attribute.**
|
||||
- Do not cite if the <source> tag does not contain an id attribute.
|
||||
- Do not use XML tags in your response.
|
||||
- Ensure citations are concise and directly related to the information provided.
|
||||
|
||||
### Example of Citation:
|
||||
If the user asks about a specific topic and the information is found in "whitepaper.pdf" with a provided <source_id>, the response should include the citation like so:
|
||||
* "According to the study, the proposed method increases efficiency by 20% [whitepaper.pdf]."
|
||||
If no <source_id> is present, the response should omit the citation.
|
||||
If the user asks about a specific topic and the information is found in a source with a provided id attribute, the response should include the citation like in the following example:
|
||||
* "According to the study, the proposed method increases efficiency by 20% [1]."
|
||||
|
||||
### Output:
|
||||
Provide a clear and direct response to the user's query, including inline citations in the format [source_id] only when the <source_id> tag is present in the context.
|
||||
Provide a clear and direct response to the user's query, including inline citations in the format [id] only when the <source> tag with id attribute is present in the context.
|
||||
|
||||
<context>
|
||||
{{CONTEXT}}
|
||||
|
||||
@@ -105,6 +105,8 @@ from open_webui.config import (
|
||||
OPENAI_API_CONFIGS,
|
||||
# Direct Connections
|
||||
ENABLE_DIRECT_CONNECTIONS,
|
||||
# Tool Server Configs
|
||||
TOOL_SERVER_CONNECTIONS,
|
||||
# Code Execution
|
||||
ENABLE_CODE_EXECUTION,
|
||||
CODE_EXECUTION_ENGINE,
|
||||
@@ -191,6 +193,7 @@ from open_webui.config import (
|
||||
DOCLING_SERVER_URL,
|
||||
DOCUMENT_INTELLIGENCE_ENDPOINT,
|
||||
DOCUMENT_INTELLIGENCE_KEY,
|
||||
MISTRAL_OCR_API_KEY,
|
||||
RAG_TOP_K,
|
||||
RAG_TOP_K_RERANKER,
|
||||
RAG_TEXT_SPLITTER,
|
||||
@@ -355,6 +358,7 @@ from open_webui.utils.access_control import has_access
|
||||
|
||||
from open_webui.utils.auth import (
|
||||
get_license_data,
|
||||
get_http_authorization_cred,
|
||||
decode_token,
|
||||
get_admin_user,
|
||||
get_verified_user,
|
||||
@@ -477,6 +481,15 @@ app.state.config.OPENAI_API_CONFIGS = OPENAI_API_CONFIGS
|
||||
|
||||
app.state.OPENAI_MODELS = {}
|
||||
|
||||
########################################
|
||||
#
|
||||
# TOOL SERVERS
|
||||
#
|
||||
########################################
|
||||
|
||||
app.state.config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS
|
||||
app.state.TOOL_SERVERS = []
|
||||
|
||||
########################################
|
||||
#
|
||||
# DIRECT CONNECTIONS
|
||||
@@ -582,6 +595,7 @@ app.state.config.TIKA_SERVER_URL = TIKA_SERVER_URL
|
||||
app.state.config.DOCLING_SERVER_URL = DOCLING_SERVER_URL
|
||||
app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT = DOCUMENT_INTELLIGENCE_ENDPOINT
|
||||
app.state.config.DOCUMENT_INTELLIGENCE_KEY = DOCUMENT_INTELLIGENCE_KEY
|
||||
app.state.config.MISTRAL_OCR_API_KEY = MISTRAL_OCR_API_KEY
|
||||
|
||||
app.state.config.TEXT_SPLITTER = RAG_TEXT_SPLITTER
|
||||
app.state.config.TIKTOKEN_ENCODING_NAME = TIKTOKEN_ENCODING_NAME
|
||||
@@ -862,6 +876,10 @@ async def commit_session_after_request(request: Request, call_next):
|
||||
@app.middleware("http")
|
||||
async def check_url(request: Request, call_next):
|
||||
start_time = int(time.time())
|
||||
request.state.token = get_http_authorization_cred(
|
||||
request.headers.get("Authorization")
|
||||
)
|
||||
|
||||
request.state.enable_api_key = app.state.config.ENABLE_API_KEY
|
||||
response = await call_next(request)
|
||||
process_time = int(time.time()) - start_time
|
||||
|
||||
@@ -20,6 +20,9 @@ from langchain_community.document_loaders import (
|
||||
YoutubeLoader,
|
||||
)
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from open_webui.retrieval.loaders.mistral import MistralLoader
|
||||
|
||||
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||
@@ -181,13 +184,16 @@ class Loader:
|
||||
for doc in docs
|
||||
]
|
||||
|
||||
def _is_text_file(self, file_ext: str, file_content_type: str) -> bool:
|
||||
return file_ext in known_source_ext or (
|
||||
file_content_type and file_content_type.find("text/") >= 0
|
||||
)
|
||||
|
||||
def _get_loader(self, filename: str, file_content_type: str, file_path: str):
|
||||
file_ext = filename.split(".")[-1].lower()
|
||||
|
||||
if self.engine == "tika" and self.kwargs.get("TIKA_SERVER_URL"):
|
||||
if file_ext in known_source_ext or (
|
||||
file_content_type and file_content_type.find("text/") >= 0
|
||||
):
|
||||
if self._is_text_file(file_ext, file_content_type):
|
||||
loader = TextLoader(file_path, autodetect_encoding=True)
|
||||
else:
|
||||
loader = TikaLoader(
|
||||
@@ -196,11 +202,14 @@ class Loader:
|
||||
mime_type=file_content_type,
|
||||
)
|
||||
elif self.engine == "docling" and self.kwargs.get("DOCLING_SERVER_URL"):
|
||||
loader = DoclingLoader(
|
||||
url=self.kwargs.get("DOCLING_SERVER_URL"),
|
||||
file_path=file_path,
|
||||
mime_type=file_content_type,
|
||||
)
|
||||
if self._is_text_file(file_ext, file_content_type):
|
||||
loader = TextLoader(file_path, autodetect_encoding=True)
|
||||
else:
|
||||
loader = DoclingLoader(
|
||||
url=self.kwargs.get("DOCLING_SERVER_URL"),
|
||||
file_path=file_path,
|
||||
mime_type=file_content_type,
|
||||
)
|
||||
elif (
|
||||
self.engine == "document_intelligence"
|
||||
and self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT") != ""
|
||||
@@ -222,6 +231,15 @@ class Loader:
|
||||
api_endpoint=self.kwargs.get("DOCUMENT_INTELLIGENCE_ENDPOINT"),
|
||||
api_key=self.kwargs.get("DOCUMENT_INTELLIGENCE_KEY"),
|
||||
)
|
||||
elif (
|
||||
self.engine == "mistral_ocr"
|
||||
and self.kwargs.get("MISTRAL_OCR_API_KEY") != ""
|
||||
and file_ext
|
||||
in ["pdf"] # Mistral OCR currently only supports PDF and images
|
||||
):
|
||||
loader = MistralLoader(
|
||||
api_key=self.kwargs.get("MISTRAL_OCR_API_KEY"), file_path=file_path
|
||||
)
|
||||
else:
|
||||
if file_ext == "pdf":
|
||||
loader = PyPDFLoader(
|
||||
@@ -257,9 +275,7 @@ class Loader:
|
||||
loader = UnstructuredPowerPointLoader(file_path)
|
||||
elif file_ext == "msg":
|
||||
loader = OutlookMessageLoader(file_path)
|
||||
elif file_ext in known_source_ext or (
|
||||
file_content_type and file_content_type.find("text/") >= 0
|
||||
):
|
||||
elif self._is_text_file(file_ext, file_content_type):
|
||||
loader = TextLoader(file_path, autodetect_encoding=True)
|
||||
else:
|
||||
loader = TextLoader(file_path, autodetect_encoding=True)
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
import requests
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from open_webui.env import SRC_LOG_LEVELS, GLOBAL_LOG_LEVEL
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||
|
||||
|
||||
class MistralLoader:
|
||||
"""
|
||||
Loads documents by processing them through the Mistral OCR API.
|
||||
"""
|
||||
|
||||
BASE_API_URL = "https://api.mistral.ai/v1"
|
||||
|
||||
def __init__(self, api_key: str, file_path: str):
|
||||
"""
|
||||
Initializes the loader.
|
||||
|
||||
Args:
|
||||
api_key: Your Mistral API key.
|
||||
file_path: The local path to the PDF file to process.
|
||||
"""
|
||||
if not api_key:
|
||||
raise ValueError("API key cannot be empty.")
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"File not found at {file_path}")
|
||||
|
||||
self.api_key = api_key
|
||||
self.file_path = file_path
|
||||
self.headers = {"Authorization": f"Bearer {self.api_key}"}
|
||||
|
||||
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
||||
"""Checks response status and returns JSON content."""
|
||||
try:
|
||||
response.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
|
||||
# Handle potential empty responses for certain successful requests (e.g., DELETE)
|
||||
if response.status_code == 204 or not response.content:
|
||||
return {} # Return empty dict if no content
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
log.error(f"HTTP error occurred: {http_err} - Response: {response.text}")
|
||||
raise
|
||||
except requests.exceptions.RequestException as req_err:
|
||||
log.error(f"Request exception occurred: {req_err}")
|
||||
raise
|
||||
except ValueError as json_err: # Includes JSONDecodeError
|
||||
log.error(f"JSON decode error: {json_err} - Response: {response.text}")
|
||||
raise # Re-raise after logging
|
||||
|
||||
def _upload_file(self) -> str:
|
||||
"""Uploads the file to Mistral for OCR processing."""
|
||||
log.info("Uploading file to Mistral API")
|
||||
url = f"{self.BASE_API_URL}/files"
|
||||
file_name = os.path.basename(self.file_path)
|
||||
|
||||
try:
|
||||
with open(self.file_path, "rb") as f:
|
||||
files = {"file": (file_name, f, "application/pdf")}
|
||||
data = {"purpose": "ocr"}
|
||||
|
||||
upload_headers = self.headers.copy() # Avoid modifying self.headers
|
||||
|
||||
response = requests.post(
|
||||
url, headers=upload_headers, files=files, data=data
|
||||
)
|
||||
|
||||
response_data = self._handle_response(response)
|
||||
file_id = response_data.get("id")
|
||||
if not file_id:
|
||||
raise ValueError("File ID not found in upload response.")
|
||||
log.info(f"File uploaded successfully. File ID: {file_id}")
|
||||
return file_id
|
||||
except Exception as e:
|
||||
log.error(f"Failed to upload file: {e}")
|
||||
raise
|
||||
|
||||
def _get_signed_url(self, file_id: str) -> str:
|
||||
"""Retrieves a temporary signed URL for the uploaded file."""
|
||||
log.info(f"Getting signed URL for file ID: {file_id}")
|
||||
url = f"{self.BASE_API_URL}/files/{file_id}/url"
|
||||
params = {"expiry": 1}
|
||||
signed_url_headers = {**self.headers, "Accept": "application/json"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=signed_url_headers, params=params)
|
||||
response_data = self._handle_response(response)
|
||||
signed_url = response_data.get("url")
|
||||
if not signed_url:
|
||||
raise ValueError("Signed URL not found in response.")
|
||||
log.info("Signed URL received.")
|
||||
return signed_url
|
||||
except Exception as e:
|
||||
log.error(f"Failed to get signed URL: {e}")
|
||||
raise
|
||||
|
||||
def _process_ocr(self, signed_url: str) -> Dict[str, Any]:
|
||||
"""Sends the signed URL to the OCR endpoint for processing."""
|
||||
log.info("Processing OCR via Mistral API")
|
||||
url = f"{self.BASE_API_URL}/ocr"
|
||||
ocr_headers = {
|
||||
**self.headers,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
payload = {
|
||||
"model": "mistral-ocr-latest",
|
||||
"document": {
|
||||
"type": "document_url",
|
||||
"document_url": signed_url,
|
||||
},
|
||||
"include_image_base64": False,
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=ocr_headers, json=payload)
|
||||
ocr_response = self._handle_response(response)
|
||||
log.info("OCR processing done.")
|
||||
log.debug("OCR response: %s", ocr_response)
|
||||
return ocr_response
|
||||
except Exception as e:
|
||||
log.error(f"Failed during OCR processing: {e}")
|
||||
raise
|
||||
|
||||
def _delete_file(self, file_id: str) -> None:
|
||||
"""Deletes the file from Mistral storage."""
|
||||
log.info(f"Deleting uploaded file ID: {file_id}")
|
||||
url = f"{self.BASE_API_URL}/files/{file_id}"
|
||||
# No specific Accept header needed, default or Authorization is usually sufficient
|
||||
|
||||
try:
|
||||
response = requests.delete(url, headers=self.headers)
|
||||
delete_response = self._handle_response(
|
||||
response
|
||||
) # Check status, ignore response body unless needed
|
||||
log.info(
|
||||
f"File deleted successfully: {delete_response}"
|
||||
) # Log the response if available
|
||||
except Exception as e:
|
||||
# Log error but don't necessarily halt execution if deletion fails
|
||||
log.error(f"Failed to delete file ID {file_id}: {e}")
|
||||
# Depending on requirements, you might choose to raise the error here
|
||||
|
||||
def load(self) -> List[Document]:
|
||||
"""
|
||||
Executes the full OCR workflow: upload, get URL, process OCR, delete file.
|
||||
|
||||
Returns:
|
||||
A list of Document objects, one for each page processed.
|
||||
"""
|
||||
file_id = None
|
||||
try:
|
||||
# 1. Upload file
|
||||
file_id = self._upload_file()
|
||||
|
||||
# 2. Get Signed URL
|
||||
signed_url = self._get_signed_url(file_id)
|
||||
|
||||
# 3. Process OCR
|
||||
ocr_response = self._process_ocr(signed_url)
|
||||
|
||||
# 4. Process results
|
||||
pages_data = ocr_response.get("pages")
|
||||
if not pages_data:
|
||||
log.warning("No pages found in OCR response.")
|
||||
return [Document(page_content="No text content found", metadata={})]
|
||||
|
||||
documents = []
|
||||
total_pages = len(pages_data)
|
||||
for page_data in pages_data:
|
||||
page_content = page_data.get("markdown")
|
||||
page_index = page_data.get("index") # API uses 0-based index
|
||||
|
||||
if page_content is not None and page_index is not None:
|
||||
documents.append(
|
||||
Document(
|
||||
page_content=page_content,
|
||||
metadata={
|
||||
"page": page_index, # 0-based index from API
|
||||
"page_label": page_index
|
||||
+ 1, # 1-based label for convenience
|
||||
"total_pages": total_pages,
|
||||
# Add other relevant metadata from page_data if available/needed
|
||||
# e.g., page_data.get('width'), page_data.get('height')
|
||||
},
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.warning(
|
||||
f"Skipping page due to missing 'markdown' or 'index'. Data: {page_data}"
|
||||
)
|
||||
|
||||
if not documents:
|
||||
# Case where pages existed but none had valid markdown/index
|
||||
log.warning(
|
||||
"OCR response contained pages, but none had valid content/index."
|
||||
)
|
||||
return [
|
||||
Document(
|
||||
page_content="No text content found in valid pages", metadata={}
|
||||
)
|
||||
]
|
||||
|
||||
return documents
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred during the loading process: {e}")
|
||||
# Return an empty list or a specific error document on failure
|
||||
return [Document(page_content=f"Error during processing: {e}", metadata={})]
|
||||
finally:
|
||||
# 5. Delete file (attempt even if prior steps failed after upload)
|
||||
if file_id:
|
||||
try:
|
||||
self._delete_file(file_id)
|
||||
except Exception as del_e:
|
||||
# Log deletion error, but don't overwrite original error if one occurred
|
||||
log.error(
|
||||
f"Cleanup error: Could not delete file ID {file_id}. Reason: {del_e}"
|
||||
)
|
||||
@@ -320,10 +320,13 @@ def query_collection_with_hybrid_search(
|
||||
log.exception(f"Error when querying the collection with hybrid_search: {e}")
|
||||
return None, e
|
||||
|
||||
# Prepare tasks for all collections and queries
|
||||
# Avoid running any tasks for collections that failed to fetch data (have assigned None)
|
||||
tasks = [
|
||||
(collection_name, query)
|
||||
for collection_name in collection_names
|
||||
for query in queries
|
||||
(cn, q)
|
||||
for cn in collection_names
|
||||
if collection_results[cn] is not None
|
||||
for q in queries
|
||||
]
|
||||
|
||||
with ThreadPoolExecutor() as executor:
|
||||
@@ -354,7 +357,7 @@ def get_embedding_function(
|
||||
):
|
||||
if embedding_engine == "":
|
||||
return lambda query, prefix=None, user=None: embedding_function.encode(
|
||||
query, prompt=prefix if prefix else None
|
||||
query, **({"prompt": prefix} if prefix else {})
|
||||
).tolist()
|
||||
elif embedding_engine in ["ollama", "openai"]:
|
||||
func = lambda query, prefix=None, user=None: generate_embeddings(
|
||||
|
||||
@@ -194,8 +194,8 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
ciphers=LDAP_CIPHERS,
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred on TLS: {str(e)}")
|
||||
raise HTTPException(400, detail=str(e))
|
||||
log.error(f"TLS configuration error: {str(e)}")
|
||||
raise HTTPException(400, detail="Failed to configure TLS for LDAP connection.")
|
||||
|
||||
try:
|
||||
server = Server(
|
||||
@@ -232,7 +232,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
username = str(entry[f"{LDAP_ATTRIBUTE_FOR_USERNAME}"]).lower()
|
||||
email = str(entry[f"{LDAP_ATTRIBUTE_FOR_MAIL}"])
|
||||
if not email or email == "" or email == "[]":
|
||||
raise HTTPException(400, f"User {form_data.user} does not have email.")
|
||||
raise HTTPException(400, "User does not have a valid email address.")
|
||||
else:
|
||||
email = email.lower()
|
||||
|
||||
@@ -248,7 +248,7 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
authentication="SIMPLE",
|
||||
)
|
||||
if not connection_user.bind():
|
||||
raise HTTPException(400, f"Authentication failed for {form_data.user}")
|
||||
raise HTTPException(400, "Authentication failed.")
|
||||
|
||||
user = Users.get_user_by_email(email)
|
||||
if not user:
|
||||
@@ -276,7 +276,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as err:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
||||
log.error(f"LDAP user creation error: {str(err)}")
|
||||
raise HTTPException(
|
||||
500, detail="Internal error occurred during LDAP user creation."
|
||||
)
|
||||
|
||||
user = Auths.authenticate_user_by_trusted_header(email)
|
||||
|
||||
@@ -312,12 +315,10 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
else:
|
||||
raise HTTPException(
|
||||
400,
|
||||
f"User {form_data.user} does not match the record. Search result: {str(entry[f'{LDAP_ATTRIBUTE_FOR_USERNAME}'])}",
|
||||
)
|
||||
raise HTTPException(400, "User record mismatch.")
|
||||
except Exception as e:
|
||||
raise HTTPException(400, detail=str(e))
|
||||
log.error(f"LDAP authentication error: {str(e)}")
|
||||
raise HTTPException(400, detail="LDAP authentication failed.")
|
||||
|
||||
|
||||
############################
|
||||
@@ -519,7 +520,8 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
|
||||
else:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
|
||||
except Exception as err:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
||||
log.error(f"Signup error: {str(err)}")
|
||||
raise HTTPException(500, detail="An internal error occurred during signup.")
|
||||
|
||||
|
||||
@router.get("/signout")
|
||||
@@ -547,7 +549,11 @@ async def signout(request: Request, response: Response):
|
||||
detail="Failed to fetch OpenID configuration",
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
log.error(f"OpenID signout error: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Failed to sign out from the OpenID provider.",
|
||||
)
|
||||
|
||||
return {"status": True}
|
||||
|
||||
@@ -591,7 +597,10 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
|
||||
else:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
|
||||
except Exception as err:
|
||||
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
||||
log.error(f"Add user error: {str(err)}")
|
||||
raise HTTPException(
|
||||
500, detail="An internal error occurred while adding the user."
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
@@ -764,11 +773,6 @@ async def update_ldap_server(
|
||||
if not value:
|
||||
raise HTTPException(400, detail=f"Required field {key} is empty")
|
||||
|
||||
if form_data.use_tls and not form_data.certificate_path:
|
||||
raise HTTPException(
|
||||
400, detail="TLS is enabled but certificate file path is missing"
|
||||
)
|
||||
|
||||
request.app.state.config.LDAP_SERVER_LABEL = form_data.label
|
||||
request.app.state.config.LDAP_SERVER_HOST = form_data.host
|
||||
request.app.state.config.LDAP_SERVER_PORT = form_data.port
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Depends, Request, HTTPException
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from typing import Optional
|
||||
|
||||
@@ -7,6 +7,8 @@ from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.config import get_config, save_config
|
||||
from open_webui.config import BannerModel
|
||||
|
||||
from open_webui.utils.tools import get_tool_server_data, get_tool_servers_data
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -66,6 +68,75 @@ async def set_direct_connections_config(
|
||||
}
|
||||
|
||||
|
||||
############################
|
||||
# ToolServers Config
|
||||
############################
|
||||
|
||||
|
||||
class ToolServerConnection(BaseModel):
|
||||
url: str
|
||||
path: str
|
||||
auth_type: Optional[str]
|
||||
key: Optional[str]
|
||||
config: Optional[dict]
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class ToolServersConfigForm(BaseModel):
|
||||
TOOL_SERVER_CONNECTIONS: list[ToolServerConnection]
|
||||
|
||||
|
||||
@router.get("/tool_servers", response_model=ToolServersConfigForm)
|
||||
async def get_tool_servers_config(request: Request, user=Depends(get_admin_user)):
|
||||
return {
|
||||
"TOOL_SERVER_CONNECTIONS": request.app.state.config.TOOL_SERVER_CONNECTIONS,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/tool_servers", response_model=ToolServersConfigForm)
|
||||
async def set_tool_servers_config(
|
||||
request: Request,
|
||||
form_data: ToolServersConfigForm,
|
||||
user=Depends(get_admin_user),
|
||||
):
|
||||
request.app.state.config.TOOL_SERVER_CONNECTIONS = [
|
||||
connection.model_dump() for connection in form_data.TOOL_SERVER_CONNECTIONS
|
||||
]
|
||||
|
||||
request.app.state.TOOL_SERVERS = await get_tool_servers_data(
|
||||
request.app.state.config.TOOL_SERVER_CONNECTIONS
|
||||
)
|
||||
|
||||
return {
|
||||
"TOOL_SERVER_CONNECTIONS": request.app.state.config.TOOL_SERVER_CONNECTIONS,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/tool_servers/verify")
|
||||
async def verify_tool_servers_config(
|
||||
request: Request, form_data: ToolServerConnection, user=Depends(get_admin_user)
|
||||
):
|
||||
"""
|
||||
Verify the connection to the tool server.
|
||||
"""
|
||||
try:
|
||||
|
||||
token = None
|
||||
if form_data.auth_type == "bearer":
|
||||
token = form_data.key
|
||||
elif form_data.auth_type == "session":
|
||||
token = request.state.token.credentials
|
||||
|
||||
url = f"{form_data.url}/{form_data.path}"
|
||||
return await get_tool_server_data(token, url)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Failed to connect to the tool server: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
############################
|
||||
# CodeInterpreterConfig
|
||||
############################
|
||||
|
||||
@@ -122,6 +122,7 @@ def upload_file(
|
||||
]:
|
||||
file_path = Storage.get_file(file_path)
|
||||
result = transcribe(request, file_path)
|
||||
|
||||
process_file(
|
||||
request,
|
||||
ProcessFileForm(file_id=id, content=result.get("text", "")),
|
||||
@@ -129,7 +130,8 @@ def upload_file(
|
||||
)
|
||||
elif file.content_type not in ["image/png", "image/jpeg", "image/gif"]:
|
||||
process_file(request, ProcessFileForm(file_id=id), user=user)
|
||||
file_item = Files.get_file_by_id(id=id)
|
||||
|
||||
file_item = Files.get_file_by_id(id=id)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error(f"Error processing file: {file_item.id}")
|
||||
@@ -162,11 +164,16 @@ def upload_file(
|
||||
|
||||
|
||||
@router.get("/", response_model=list[FileModelResponse])
|
||||
async def list_files(user=Depends(get_verified_user)):
|
||||
async def list_files(user=Depends(get_verified_user), content: bool = Query(True)):
|
||||
if user.role == "admin":
|
||||
files = Files.get_files()
|
||||
else:
|
||||
files = Files.get_files_by_user_id(user.id)
|
||||
|
||||
if not content:
|
||||
for file in files:
|
||||
del file.data["content"]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
|
||||
@@ -236,7 +236,8 @@ async def delete_folder_by_id(
|
||||
chat_delete_permission = has_permission(
|
||||
user.id, "chat.delete", request.app.state.config.USER_PERMISSIONS
|
||||
)
|
||||
if not chat_delete_permission:
|
||||
|
||||
if user.role != "admin" and not chat_delete_permission:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
|
||||
@@ -1197,7 +1197,7 @@ class OpenAIChatMessageContent(BaseModel):
|
||||
|
||||
class OpenAIChatMessage(BaseModel):
|
||||
role: str
|
||||
content: Union[str, list[OpenAIChatMessageContent]]
|
||||
content: Union[Optional[str], list[OpenAIChatMessageContent]]
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def get_ef(
|
||||
|
||||
|
||||
def get_rf(
|
||||
reranking_model: str,
|
||||
reranking_model: Optional[str] = None,
|
||||
auto_update: bool = False,
|
||||
):
|
||||
rf = None
|
||||
@@ -150,8 +150,8 @@ def get_rf(
|
||||
device=DEVICE_TYPE,
|
||||
trust_remote_code=RAG_RERANKING_MODEL_TRUST_REMOTE_CODE,
|
||||
)
|
||||
except:
|
||||
log.error("CrossEncoder error")
|
||||
except Exception as e:
|
||||
log.error(f"CrossEncoder: {e}")
|
||||
raise Exception(ERROR_MESSAGES.DEFAULT("CrossEncoder error"))
|
||||
return rf
|
||||
|
||||
@@ -174,7 +174,7 @@ class ProcessUrlForm(CollectionNameForm):
|
||||
url: str
|
||||
|
||||
|
||||
class SearchForm(CollectionNameForm):
|
||||
class SearchForm(BaseModel):
|
||||
query: str
|
||||
|
||||
|
||||
@@ -364,6 +364,9 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
||||
"endpoint": request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
|
||||
"key": request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
|
||||
},
|
||||
"mistral_ocr_config": {
|
||||
"api_key": request.app.state.config.MISTRAL_OCR_API_KEY,
|
||||
},
|
||||
},
|
||||
"chunk": {
|
||||
"text_splitter": request.app.state.config.TEXT_SPLITTER,
|
||||
@@ -427,11 +430,16 @@ class DocumentIntelligenceConfigForm(BaseModel):
|
||||
key: str
|
||||
|
||||
|
||||
class MistralOCRConfigForm(BaseModel):
|
||||
api_key: str
|
||||
|
||||
|
||||
class ContentExtractionConfig(BaseModel):
|
||||
engine: str = ""
|
||||
tika_server_url: Optional[str] = None
|
||||
docling_server_url: Optional[str] = None
|
||||
document_intelligence_config: Optional[DocumentIntelligenceConfigForm] = None
|
||||
mistral_ocr_config: Optional[MistralOCRConfigForm] = None
|
||||
|
||||
|
||||
class ChunkParamUpdateForm(BaseModel):
|
||||
@@ -553,6 +561,10 @@ async def update_rag_config(
|
||||
request.app.state.config.DOCUMENT_INTELLIGENCE_KEY = (
|
||||
form_data.content_extraction.document_intelligence_config.key
|
||||
)
|
||||
if form_data.content_extraction.mistral_ocr_config is not None:
|
||||
request.app.state.config.MISTRAL_OCR_API_KEY = (
|
||||
form_data.content_extraction.mistral_ocr_config.api_key
|
||||
)
|
||||
|
||||
if form_data.chunk is not None:
|
||||
request.app.state.config.TEXT_SPLITTER = form_data.chunk.text_splitter
|
||||
@@ -659,6 +671,9 @@ async def update_rag_config(
|
||||
"endpoint": request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
|
||||
"key": request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
|
||||
},
|
||||
"mistral_ocr_config": {
|
||||
"api_key": request.app.state.config.MISTRAL_OCR_API_KEY,
|
||||
},
|
||||
},
|
||||
"chunk": {
|
||||
"text_splitter": request.app.state.config.TEXT_SPLITTER,
|
||||
@@ -747,6 +762,9 @@ async def update_query_settings(
|
||||
form_data.hybrid if form_data.hybrid else False
|
||||
)
|
||||
|
||||
if not request.app.state.config.ENABLE_RAG_HYBRID_SEARCH:
|
||||
request.app.state.rf = None
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"template": request.app.state.config.RAG_TEMPLATE,
|
||||
@@ -940,7 +958,7 @@ def process_file(
|
||||
|
||||
if form_data.content:
|
||||
# Update the content in the file
|
||||
# Usage: /files/{file_id}/data/content/update
|
||||
# Usage: /files/{file_id}/data/content/update, /files/ (audio file upload pipeline)
|
||||
|
||||
try:
|
||||
# /files/{file_id}/data/content/update
|
||||
@@ -1007,6 +1025,7 @@ def process_file(
|
||||
PDF_EXTRACT_IMAGES=request.app.state.config.PDF_EXTRACT_IMAGES,
|
||||
DOCUMENT_INTELLIGENCE_ENDPOINT=request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT,
|
||||
DOCUMENT_INTELLIGENCE_KEY=request.app.state.config.DOCUMENT_INTELLIGENCE_KEY,
|
||||
MISTRAL_OCR_API_KEY=request.app.state.config.MISTRAL_OCR_API_KEY,
|
||||
)
|
||||
docs = loader.load(
|
||||
file.filename, file.meta.get("content_type"), file_path
|
||||
@@ -1445,12 +1464,6 @@ async def process_web_search(
|
||||
log.debug(f"web_results: {web_results}")
|
||||
|
||||
try:
|
||||
collection_name = form_data.collection_name
|
||||
if collection_name == "" or collection_name is None:
|
||||
collection_name = f"web-search-{calculate_sha256_string(form_data.query)}"[
|
||||
:63
|
||||
]
|
||||
|
||||
urls = [result.link for result in web_results]
|
||||
loader = get_web_loader(
|
||||
urls,
|
||||
@@ -1459,6 +1472,9 @@ async def process_web_search(
|
||||
trust_env=request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
|
||||
)
|
||||
docs = await loader.aload()
|
||||
urls = [
|
||||
doc.metadata["source"] for doc in docs
|
||||
] # only keep URLs which could be retrieved
|
||||
|
||||
if request.app.state.config.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL:
|
||||
return {
|
||||
@@ -1475,18 +1491,26 @@ async def process_web_search(
|
||||
"loaded_count": len(docs),
|
||||
}
|
||||
else:
|
||||
await run_in_threadpool(
|
||||
save_docs_to_vector_db,
|
||||
request,
|
||||
docs,
|
||||
collection_name,
|
||||
overwrite=True,
|
||||
user=user,
|
||||
)
|
||||
collection_names = []
|
||||
for doc_idx, doc in enumerate(docs):
|
||||
collection_name = f"web-search-{calculate_sha256_string(form_data.query + '-' + urls[doc_idx])}"[
|
||||
:63
|
||||
]
|
||||
|
||||
collection_names.append(collection_name)
|
||||
|
||||
await run_in_threadpool(
|
||||
save_docs_to_vector_db,
|
||||
request,
|
||||
[doc],
|
||||
collection_name,
|
||||
overwrite=True,
|
||||
user=user,
|
||||
)
|
||||
|
||||
return {
|
||||
"status": True,
|
||||
"collection_name": collection_name,
|
||||
"collection_names": collection_names,
|
||||
"filenames": urls,
|
||||
"loaded_count": len(docs),
|
||||
}
|
||||
@@ -1515,8 +1539,13 @@ def query_doc_handler(
|
||||
):
|
||||
try:
|
||||
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH:
|
||||
collection_results = {}
|
||||
collection_results[form_data.collection_name] = VECTOR_DB_CLIENT.get(
|
||||
collection_name=form_data.collection_name
|
||||
)
|
||||
return query_doc_with_hybrid_search(
|
||||
collection_name=form_data.collection_name,
|
||||
collection_result=collection_results[form_data.collection_name],
|
||||
query=form_data.query,
|
||||
embedding_function=lambda query, prefix: request.app.state.EMBEDDING_FUNCTION(
|
||||
query, prefix=prefix, user=user
|
||||
|
||||
@@ -653,17 +653,6 @@ async def generate_moa_response(
|
||||
detail="Model not found",
|
||||
)
|
||||
|
||||
# Check if the user has a custom task model
|
||||
# If the user has a custom task model, use that model
|
||||
task_model_id = get_task_model_id(
|
||||
model_id,
|
||||
request.app.state.config.TASK_MODEL,
|
||||
request.app.state.config.TASK_MODEL_EXTERNAL,
|
||||
models,
|
||||
)
|
||||
|
||||
log.debug(f"generating MOA model {task_model_id} for user {user.email} ")
|
||||
|
||||
template = DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE
|
||||
|
||||
content = moa_response_generation_template(
|
||||
@@ -673,7 +662,7 @@ async def generate_moa_response(
|
||||
)
|
||||
|
||||
payload = {
|
||||
"model": task_model_id,
|
||||
"model": model_id,
|
||||
"messages": [{"role": "user", "content": content}],
|
||||
"stream": form_data.get("stream", False),
|
||||
"metadata": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import time
|
||||
|
||||
from open_webui.models.tools import (
|
||||
ToolForm,
|
||||
@@ -18,6 +19,8 @@ from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_access, has_permission
|
||||
from open_webui.env import SRC_LOG_LEVELS
|
||||
|
||||
from open_webui.utils.tools import get_tool_servers_data
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
||||
|
||||
@@ -30,11 +33,51 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=list[ToolUserResponse])
|
||||
async def get_tools(user=Depends(get_verified_user)):
|
||||
if user.role == "admin":
|
||||
tools = Tools.get_tools()
|
||||
else:
|
||||
tools = Tools.get_tools_by_user_id(user.id, "read")
|
||||
async def get_tools(request: Request, user=Depends(get_verified_user)):
|
||||
|
||||
if not request.app.state.TOOL_SERVERS:
|
||||
# If the tool servers are not set, we need to set them
|
||||
# This is done only once when the server starts
|
||||
# This is done to avoid loading the tool servers every time
|
||||
|
||||
request.app.state.TOOL_SERVERS = await get_tool_servers_data(
|
||||
request.app.state.config.TOOL_SERVER_CONNECTIONS
|
||||
)
|
||||
|
||||
tools = Tools.get_tools()
|
||||
for idx, server in enumerate(request.app.state.TOOL_SERVERS):
|
||||
tools.append(
|
||||
ToolUserResponse(
|
||||
**{
|
||||
"id": f"server:{server['idx']}",
|
||||
"user_id": f"server:{server['idx']}",
|
||||
"name": server["openapi"]
|
||||
.get("info", {})
|
||||
.get("title", "Tool Server"),
|
||||
"meta": {
|
||||
"description": server["openapi"]
|
||||
.get("info", {})
|
||||
.get("description", ""),
|
||||
},
|
||||
"access_control": request.app.state.config.TOOL_SERVER_CONNECTIONS[
|
||||
idx
|
||||
]
|
||||
.get("config", {})
|
||||
.get("access_control", None),
|
||||
"updated_at": int(time.time()),
|
||||
"created_at": int(time.time()),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if user.role != "admin":
|
||||
tools = [
|
||||
tool
|
||||
for tool in tools
|
||||
if tool.user_id == user.id
|
||||
or has_access(user.id, "read", tool.access_control)
|
||||
]
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ class ChatPermissions(BaseModel):
|
||||
|
||||
|
||||
class FeaturesPermissions(BaseModel):
|
||||
direct_tool_servers: bool = False
|
||||
web_search: bool = True
|
||||
image_generation: bool = True
|
||||
code_interpreter: bool = True
|
||||
|
||||
@@ -8,7 +8,9 @@ import requests
|
||||
import os
|
||||
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
from pytz import UTC
|
||||
from typing import Optional, Union, List, Dict
|
||||
|
||||
from open_webui.models.users import Users
|
||||
@@ -141,12 +143,14 @@ def create_api_key():
|
||||
return f"sk-{key}"
|
||||
|
||||
|
||||
def get_http_authorization_cred(auth_header: str):
|
||||
def get_http_authorization_cred(auth_header: Optional[str]):
|
||||
if not auth_header:
|
||||
return None
|
||||
try:
|
||||
scheme, credentials = auth_header.split(" ")
|
||||
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
|
||||
except Exception:
|
||||
raise ValueError(ERROR_MESSAGES.INVALID_TOKEN)
|
||||
return None
|
||||
|
||||
|
||||
def get_current_user(
|
||||
@@ -180,7 +184,12 @@ def get_current_user(
|
||||
).split(",")
|
||||
]
|
||||
|
||||
if request.url.path not in allowed_paths:
|
||||
# Check if the request path matches any allowed endpoint.
|
||||
if not any(
|
||||
request.url.path == allowed
|
||||
or request.url.path.startswith(allowed + "/")
|
||||
for allowed in allowed_paths
|
||||
):
|
||||
raise HTTPException(
|
||||
status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED
|
||||
)
|
||||
|
||||
@@ -12,9 +12,9 @@ log.setLevel(SRC_LOG_LEVELS["MAIN"])
|
||||
def get_sorted_filter_ids(model: dict):
|
||||
def get_priority(function_id):
|
||||
function = Functions.get_function_by_id(function_id)
|
||||
if function is not None and hasattr(function, "valves"):
|
||||
# TODO: Fix FunctionModel to include vavles
|
||||
return (function.valves if function.valves else {}).get("priority", 0)
|
||||
if function is not None:
|
||||
valves = Functions.get_function_valves_by_id(function_id)
|
||||
return valves.get("priority", 0) if valves else 0
|
||||
return 0
|
||||
|
||||
filter_ids = [function.id for function in Functions.get_global_filter_functions()]
|
||||
|
||||
@@ -221,13 +221,23 @@ async def chat_completion_tools_handler(
|
||||
except Exception as e:
|
||||
tool_result = str(e)
|
||||
|
||||
tool_result_files = []
|
||||
if isinstance(tool_result, list):
|
||||
for item in tool_result:
|
||||
# check if string
|
||||
if isinstance(item, str) and item.startswith("data:"):
|
||||
tool_result_files.append(item)
|
||||
tool_result.remove(item)
|
||||
|
||||
if isinstance(tool_result, dict) or isinstance(tool_result, list):
|
||||
tool_result = json.dumps(tool_result, indent=2)
|
||||
|
||||
if isinstance(tool_result, str):
|
||||
tool = tools[tool_function_name]
|
||||
tool_id = tool.get("toolkit_id", "")
|
||||
if tool.get("citation", False) or tool.get("direct", False):
|
||||
tool_id = tool.get("tool_id", "")
|
||||
if tool.get("metadata", {}).get("citation", False) or tool.get(
|
||||
"direct", False
|
||||
):
|
||||
|
||||
sources.append(
|
||||
{
|
||||
@@ -238,7 +248,7 @@ async def chat_completion_tools_handler(
|
||||
else f"{tool_function_name}"
|
||||
),
|
||||
},
|
||||
"document": [tool_result],
|
||||
"document": [tool_result, *tool_result_files],
|
||||
"metadata": [
|
||||
{
|
||||
"source": (
|
||||
@@ -254,7 +264,7 @@ async def chat_completion_tools_handler(
|
||||
sources.append(
|
||||
{
|
||||
"source": {},
|
||||
"document": [tool_result],
|
||||
"document": [tool_result, *tool_result_files],
|
||||
"metadata": [
|
||||
{
|
||||
"source": (
|
||||
@@ -267,7 +277,11 @@ async def chat_completion_tools_handler(
|
||||
}
|
||||
)
|
||||
|
||||
if tools[tool_function_name].get("file_handler", False):
|
||||
if (
|
||||
tools[tool_function_name]
|
||||
.get("metadata", {})
|
||||
.get("file_handler", False)
|
||||
):
|
||||
skip_files = True
|
||||
|
||||
# check if "tool_calls" in result
|
||||
@@ -385,24 +399,44 @@ async def chat_web_search_handler(
|
||||
all_results.append(results)
|
||||
files = form_data.get("files", [])
|
||||
|
||||
if results.get("collection_name"):
|
||||
files.append(
|
||||
{
|
||||
"collection_name": results["collection_name"],
|
||||
"name": searchQuery,
|
||||
"type": "web_search",
|
||||
"urls": results["filenames"],
|
||||
}
|
||||
)
|
||||
if results.get("collection_names"):
|
||||
for col_idx, collection_name in enumerate(
|
||||
results.get("collection_names")
|
||||
):
|
||||
files.append(
|
||||
{
|
||||
"collection_name": collection_name,
|
||||
"name": searchQuery,
|
||||
"type": "web_search",
|
||||
"urls": [results["filenames"][col_idx]],
|
||||
}
|
||||
)
|
||||
elif results.get("docs"):
|
||||
files.append(
|
||||
{
|
||||
"docs": results.get("docs", []),
|
||||
"name": searchQuery,
|
||||
"type": "web_search",
|
||||
"urls": results["filenames"],
|
||||
}
|
||||
)
|
||||
# Invoked when bypass embedding and retrieval is set to True
|
||||
docs = results["docs"]
|
||||
|
||||
if len(docs) == len(results["filenames"]):
|
||||
# the number of docs and filenames (urls) should be the same
|
||||
for doc_idx, doc in enumerate(docs):
|
||||
files.append(
|
||||
{
|
||||
"docs": [doc],
|
||||
"name": searchQuery,
|
||||
"type": "web_search",
|
||||
"urls": [results["filenames"][doc_idx]],
|
||||
}
|
||||
)
|
||||
else:
|
||||
# edge case when the number of docs and filenames (urls) are not the same
|
||||
# this should not happen, but if it does, we will just append the docs
|
||||
files.append(
|
||||
{
|
||||
"docs": results.get("docs", []),
|
||||
"name": searchQuery,
|
||||
"type": "web_search",
|
||||
"urls": results["filenames"],
|
||||
}
|
||||
)
|
||||
|
||||
form_data["files"] = files
|
||||
except Exception as e:
|
||||
@@ -625,27 +659,28 @@ def apply_params_to_form_data(form_data, model):
|
||||
if "keep_alive" in params:
|
||||
form_data["keep_alive"] = params["keep_alive"]
|
||||
else:
|
||||
if "seed" in params:
|
||||
if "seed" in params and params["seed"] is not None:
|
||||
form_data["seed"] = params["seed"]
|
||||
|
||||
if "stop" in params:
|
||||
if "stop" in params and params["stop"] is not None:
|
||||
form_data["stop"] = params["stop"]
|
||||
|
||||
if "temperature" in params:
|
||||
if "temperature" in params and params["temperature"] is not None:
|
||||
form_data["temperature"] = params["temperature"]
|
||||
|
||||
if "max_tokens" in params:
|
||||
if "max_tokens" in params and params["max_tokens"] is not None:
|
||||
form_data["max_tokens"] = params["max_tokens"]
|
||||
|
||||
if "top_p" in params:
|
||||
if "top_p" in params and params["top_p"] is not None:
|
||||
form_data["top_p"] = params["top_p"]
|
||||
|
||||
if "frequency_penalty" in params:
|
||||
if "frequency_penalty" in params and params["frequency_penalty"] is not None:
|
||||
form_data["frequency_penalty"] = params["frequency_penalty"]
|
||||
|
||||
if "reasoning_effort" in params:
|
||||
if "reasoning_effort" in params and params["reasoning_effort"] is not None:
|
||||
form_data["reasoning_effort"] = params["reasoning_effort"]
|
||||
if "logit_bias" in params:
|
||||
|
||||
if "logit_bias" in params and params["logit_bias"] is not None:
|
||||
try:
|
||||
form_data["logit_bias"] = json.loads(
|
||||
convert_logit_bias_input_to_json(params["logit_bias"])
|
||||
@@ -865,7 +900,9 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
for source_idx, source in enumerate(sources):
|
||||
if "document" in source:
|
||||
for doc_idx, doc_context in enumerate(source["document"]):
|
||||
context_string += f"<source><source_id>{source_idx + 1}</source_id><source_context>{doc_context}</source_context></source>\n"
|
||||
context_string += (
|
||||
f'<source id="{source_idx + 1}">{doc_context}</source>\n'
|
||||
)
|
||||
|
||||
context_string = context_string.strip()
|
||||
prompt = get_last_user_message(form_data["messages"])
|
||||
@@ -1198,13 +1235,15 @@ async def process_chat_response(
|
||||
)
|
||||
|
||||
tool_result = None
|
||||
tool_result_files = None
|
||||
for result in results:
|
||||
if tool_call_id == result.get("tool_call_id", ""):
|
||||
tool_result = result.get("content", None)
|
||||
tool_result_files = result.get("files", None)
|
||||
break
|
||||
|
||||
if tool_result:
|
||||
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="true" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}" result="{html.escape(json.dumps(tool_result))}">\n<summary>Tool Executed</summary>\n</details>'
|
||||
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="true" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}" result="{html.escape(json.dumps(tool_result))}" files="{html.escape(json.dumps(tool_result_files)) if tool_result_files else ""}">\n<summary>Tool Executed</summary>\n</details>\n'
|
||||
else:
|
||||
tool_calls_display_content = f'{tool_calls_display_content}\n<details type="tool_calls" done="false" id="{tool_call_id}" name="{tool_name}" arguments="{html.escape(json.dumps(tool_arguments))}">\n<summary>Executing...</summary>\n</details>'
|
||||
|
||||
@@ -1805,7 +1844,7 @@ async def process_chat_response(
|
||||
|
||||
await stream_body_handler(response)
|
||||
|
||||
MAX_TOOL_CALL_RETRIES = 5
|
||||
MAX_TOOL_CALL_RETRIES = 10
|
||||
tool_call_retries = 0
|
||||
|
||||
while len(tool_calls) > 0 and tool_call_retries < MAX_TOOL_CALL_RETRIES:
|
||||
@@ -1898,6 +1937,14 @@ async def process_chat_response(
|
||||
except Exception as e:
|
||||
tool_result = str(e)
|
||||
|
||||
tool_result_files = []
|
||||
if isinstance(tool_result, list):
|
||||
for item in tool_result:
|
||||
# check if string
|
||||
if isinstance(item, str) and item.startswith("data:"):
|
||||
tool_result_files.append(item)
|
||||
tool_result.remove(item)
|
||||
|
||||
if isinstance(tool_result, dict) or isinstance(
|
||||
tool_result, list
|
||||
):
|
||||
@@ -1907,6 +1954,11 @@ async def process_chat_response(
|
||||
{
|
||||
"tool_call_id": tool_call_id,
|
||||
"content": tool_result,
|
||||
**(
|
||||
{"files": tool_result_files}
|
||||
if tool_result_files
|
||||
else {}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -326,40 +326,45 @@ class OAuthManager:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||
|
||||
picture_claim = auth_manager_config.OAUTH_PICTURE_CLAIM
|
||||
picture_url = user_data.get(
|
||||
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
|
||||
)
|
||||
if picture_url:
|
||||
# Download the profile image into a base64 string
|
||||
try:
|
||||
access_token = token.get("access_token")
|
||||
get_kwargs = {}
|
||||
if access_token:
|
||||
get_kwargs["headers"] = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(picture_url, **get_kwargs) as resp:
|
||||
if resp.ok:
|
||||
picture = await resp.read()
|
||||
base64_encoded_picture = base64.b64encode(
|
||||
picture
|
||||
).decode("utf-8")
|
||||
guessed_mime_type = mimetypes.guess_type(
|
||||
picture_url
|
||||
)[0]
|
||||
if guessed_mime_type is None:
|
||||
# assume JPG, browsers are tolerant enough of image formats
|
||||
guessed_mime_type = "image/jpeg"
|
||||
picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}"
|
||||
else:
|
||||
picture_url = "/user.png"
|
||||
except Exception as e:
|
||||
log.error(
|
||||
f"Error downloading profile image '{picture_url}': {e}"
|
||||
)
|
||||
if picture_claim:
|
||||
picture_url = user_data.get(
|
||||
picture_claim, OAUTH_PROVIDERS[provider].get("picture_url", "")
|
||||
)
|
||||
if picture_url:
|
||||
# Download the profile image into a base64 string
|
||||
try:
|
||||
access_token = token.get("access_token")
|
||||
get_kwargs = {}
|
||||
if access_token:
|
||||
get_kwargs["headers"] = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
picture_url, **get_kwargs
|
||||
) as resp:
|
||||
if resp.ok:
|
||||
picture = await resp.read()
|
||||
base64_encoded_picture = base64.b64encode(
|
||||
picture
|
||||
).decode("utf-8")
|
||||
guessed_mime_type = mimetypes.guess_type(
|
||||
picture_url
|
||||
)[0]
|
||||
if guessed_mime_type is None:
|
||||
# assume JPG, browsers are tolerant enough of image formats
|
||||
guessed_mime_type = "image/jpeg"
|
||||
picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}"
|
||||
else:
|
||||
picture_url = "/user.png"
|
||||
except Exception as e:
|
||||
log.error(
|
||||
f"Error downloading profile image '{picture_url}': {e}"
|
||||
)
|
||||
picture_url = "/user.png"
|
||||
if not picture_url:
|
||||
picture_url = "/user.png"
|
||||
if not picture_url:
|
||||
else:
|
||||
picture_url = "/user.png"
|
||||
|
||||
username_claim = auth_manager_config.OAUTH_USERNAME_CLAIM
|
||||
|
||||
@@ -68,23 +68,23 @@ def replace_imports(content):
|
||||
return content
|
||||
|
||||
|
||||
def load_tools_module_by_id(toolkit_id, content=None):
|
||||
def load_tools_module_by_id(tool_id, content=None):
|
||||
|
||||
if content is None:
|
||||
tool = Tools.get_tool_by_id(toolkit_id)
|
||||
tool = Tools.get_tool_by_id(tool_id)
|
||||
if not tool:
|
||||
raise Exception(f"Toolkit not found: {toolkit_id}")
|
||||
raise Exception(f"Toolkit not found: {tool_id}")
|
||||
|
||||
content = tool.content
|
||||
|
||||
content = replace_imports(content)
|
||||
Tools.update_tool_by_id(toolkit_id, {"content": content})
|
||||
Tools.update_tool_by_id(tool_id, {"content": content})
|
||||
else:
|
||||
frontmatter = extract_frontmatter(content)
|
||||
# Install required packages found within the frontmatter
|
||||
install_frontmatter_requirements(frontmatter.get("requirements", ""))
|
||||
|
||||
module_name = f"tool_{toolkit_id}"
|
||||
module_name = f"tool_{tool_id}"
|
||||
module = types.ModuleType(module_name)
|
||||
sys.modules[module_name] = module
|
||||
|
||||
@@ -108,7 +108,7 @@ def load_tools_module_by_id(toolkit_id, content=None):
|
||||
else:
|
||||
raise Exception("No Tools class found in the module")
|
||||
except Exception as e:
|
||||
log.error(f"Error loading module: {toolkit_id}: {e}")
|
||||
log.error(f"Error loading module: {tool_id}: {e}")
|
||||
del sys.modules[module_name] # Clean up
|
||||
raise e
|
||||
finally:
|
||||
|
||||
@@ -2,9 +2,10 @@ import inspect
|
||||
import logging
|
||||
import re
|
||||
import inspect
|
||||
import uuid
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
from typing import Any, Awaitable, Callable, get_type_hints
|
||||
from typing import Any, Awaitable, Callable, get_type_hints, Dict, List, Union, Optional
|
||||
from functools import update_wrapper, partial
|
||||
|
||||
|
||||
@@ -17,96 +18,162 @@ from open_webui.models.tools import Tools
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.utils.plugin import load_tools_module_by_id
|
||||
|
||||
import copy
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def apply_extra_params_to_tool_function(
|
||||
def get_async_tool_function_and_apply_extra_params(
|
||||
function: Callable, extra_params: dict
|
||||
) -> Callable[..., Awaitable]:
|
||||
sig = inspect.signature(function)
|
||||
extra_params = {k: v for k, v in extra_params.items() if k in sig.parameters}
|
||||
partial_func = partial(function, **extra_params)
|
||||
|
||||
if inspect.iscoroutinefunction(function):
|
||||
update_wrapper(partial_func, function)
|
||||
return partial_func
|
||||
else:
|
||||
# Make it a coroutine function
|
||||
async def new_function(*args, **kwargs):
|
||||
return partial_func(*args, **kwargs)
|
||||
|
||||
async def new_function(*args, **kwargs):
|
||||
return partial_func(*args, **kwargs)
|
||||
|
||||
update_wrapper(new_function, function)
|
||||
return new_function
|
||||
update_wrapper(new_function, function)
|
||||
return new_function
|
||||
|
||||
|
||||
# Mutation on extra_params
|
||||
def get_tools(
|
||||
request: Request, tool_ids: list[str], user: UserModel, extra_params: dict
|
||||
) -> dict[str, dict]:
|
||||
tools_dict = {}
|
||||
|
||||
for tool_id in tool_ids:
|
||||
tools = Tools.get_tool_by_id(tool_id)
|
||||
if tools is None:
|
||||
continue
|
||||
tool = Tools.get_tool_by_id(tool_id)
|
||||
if tool is None:
|
||||
if tool_id.startswith("server:"):
|
||||
server_idx = int(tool_id.split(":")[1])
|
||||
tool_server_connection = (
|
||||
request.app.state.config.TOOL_SERVER_CONNECTIONS[server_idx]
|
||||
)
|
||||
tool_server_data = request.app.state.TOOL_SERVERS[server_idx]
|
||||
specs = tool_server_data.get("specs", [])
|
||||
|
||||
module = request.app.state.TOOLS.get(tool_id, None)
|
||||
if module is None:
|
||||
module, _ = load_tools_module_by_id(tool_id)
|
||||
request.app.state.TOOLS[tool_id] = module
|
||||
for spec in specs:
|
||||
function_name = spec["name"]
|
||||
|
||||
extra_params["__id__"] = tool_id
|
||||
if hasattr(module, "valves") and hasattr(module, "Valves"):
|
||||
valves = Tools.get_tool_valves_by_id(tool_id) or {}
|
||||
module.valves = module.Valves(**valves)
|
||||
auth_type = tool_server_connection.get("auth_type", "bearer")
|
||||
token = None
|
||||
|
||||
if hasattr(module, "UserValves"):
|
||||
extra_params["__user__"]["valves"] = module.UserValves( # type: ignore
|
||||
**Tools.get_user_valves_by_id_and_user_id(tool_id, user.id)
|
||||
)
|
||||
if auth_type == "bearer":
|
||||
token = tool_server_connection.get("key", "")
|
||||
elif auth_type == "session":
|
||||
token = request.state.token.credentials
|
||||
|
||||
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"
|
||||
def make_tool_function(function_name, token, tool_server_data):
|
||||
async def tool_function(**kwargs):
|
||||
print(
|
||||
f"Executing tool function {function_name} with params: {kwargs}"
|
||||
)
|
||||
return await execute_tool_server(
|
||||
token=token,
|
||||
url=tool_server_data["url"],
|
||||
name=function_name,
|
||||
params=kwargs,
|
||||
server_data=tool_server_data,
|
||||
)
|
||||
|
||||
# Remove internal parameters
|
||||
spec["parameters"]["properties"] = {
|
||||
key: val
|
||||
for key, val in spec["parameters"]["properties"].items()
|
||||
if not key.startswith("__")
|
||||
}
|
||||
return tool_function
|
||||
|
||||
function_name = spec["name"]
|
||||
tool_function = make_tool_function(
|
||||
function_name, token, tool_server_data
|
||||
)
|
||||
|
||||
# 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)
|
||||
callable = get_async_tool_function_and_apply_extra_params(
|
||||
tool_function,
|
||||
{},
|
||||
)
|
||||
|
||||
if callable.__doc__ and callable.__doc__.strip() != "":
|
||||
s = re.split(":(param|return)", callable.__doc__, 1)
|
||||
spec["description"] = s[0]
|
||||
tool_dict = {
|
||||
"tool_id": tool_id,
|
||||
"callable": callable,
|
||||
"spec": spec,
|
||||
}
|
||||
|
||||
# TODO: if collision, prepend toolkit name
|
||||
if function_name in tools_dict:
|
||||
log.warning(
|
||||
f"Tool {function_name} already exists in another tools!"
|
||||
)
|
||||
log.warning(f"Discarding {tool_id}.{function_name}")
|
||||
else:
|
||||
tools_dict[function_name] = tool_dict
|
||||
else:
|
||||
spec["description"] = function_name
|
||||
continue
|
||||
else:
|
||||
module = request.app.state.TOOLS.get(tool_id, None)
|
||||
if module is None:
|
||||
module, _ = load_tools_module_by_id(tool_id)
|
||||
request.app.state.TOOLS[tool_id] = module
|
||||
|
||||
# TODO: This needs to be a pydantic model
|
||||
tool_dict = {
|
||||
"spec": spec,
|
||||
"callable": callable,
|
||||
"toolkit_id": tool_id,
|
||||
"pydantic_model": function_to_pydantic_model(callable),
|
||||
# Misc info
|
||||
"file_handler": hasattr(module, "file_handler") and module.file_handler,
|
||||
"citation": hasattr(module, "citation") and module.citation,
|
||||
}
|
||||
extra_params["__id__"] = tool_id
|
||||
|
||||
# TODO: if collision, prepend toolkit name
|
||||
if function_name in tools_dict:
|
||||
log.warning(f"Tool {function_name} already exists in another tools!")
|
||||
log.warning(f"Collision between {tools} and {tool_id}.")
|
||||
log.warning(f"Discarding {tools}.{function_name}")
|
||||
else:
|
||||
tools_dict[function_name] = tool_dict
|
||||
# Set valves for the tool
|
||||
if hasattr(module, "valves") and hasattr(module, "Valves"):
|
||||
valves = Tools.get_tool_valves_by_id(tool_id) or {}
|
||||
module.valves = module.Valves(**valves)
|
||||
if hasattr(module, "UserValves"):
|
||||
extra_params["__user__"]["valves"] = module.UserValves( # type: ignore
|
||||
**Tools.get_user_valves_by_id_and_user_id(tool_id, user.id)
|
||||
)
|
||||
|
||||
for spec in tool.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 reserved parameters (e.g. __id__, __user__)
|
||||
spec["parameters"]["properties"] = {
|
||||
key: val
|
||||
for key, val in spec["parameters"]["properties"].items()
|
||||
if not key.startswith("__")
|
||||
}
|
||||
|
||||
# convert to function that takes only model params and inserts custom params
|
||||
function_name = spec["name"]
|
||||
tool_function = getattr(module, function_name)
|
||||
callable = get_async_tool_function_and_apply_extra_params(
|
||||
tool_function, extra_params
|
||||
)
|
||||
|
||||
# TODO: Support Pydantic models as parameters
|
||||
if callable.__doc__ and callable.__doc__.strip() != "":
|
||||
s = re.split(":(param|return)", callable.__doc__, 1)
|
||||
spec["description"] = s[0]
|
||||
else:
|
||||
spec["description"] = function_name
|
||||
|
||||
tool_dict = {
|
||||
"tool_id": tool_id,
|
||||
"callable": callable,
|
||||
"spec": spec,
|
||||
# Misc info
|
||||
"metadata": {
|
||||
"file_handler": hasattr(module, "file_handler")
|
||||
and module.file_handler,
|
||||
"citation": hasattr(module, "citation") and module.citation,
|
||||
},
|
||||
}
|
||||
|
||||
# TODO: if collision, prepend toolkit name
|
||||
if function_name in tools_dict:
|
||||
log.warning(
|
||||
f"Tool {function_name} already exists in another tools!"
|
||||
)
|
||||
log.warning(f"Discarding {tool_id}.{function_name}")
|
||||
else:
|
||||
tools_dict[function_name] = tool_dict
|
||||
|
||||
return tools_dict
|
||||
|
||||
@@ -214,6 +281,273 @@ def get_callable_attributes(tool: object) -> list[Callable]:
|
||||
|
||||
|
||||
def get_tools_specs(tool_class: object) -> list[dict]:
|
||||
function_list = get_callable_attributes(tool_class)
|
||||
models = map(function_to_pydantic_model, function_list)
|
||||
return [convert_to_openai_function(tool) for tool in models]
|
||||
function_model_list = map(
|
||||
function_to_pydantic_model, get_callable_attributes(tool_class)
|
||||
)
|
||||
return [
|
||||
convert_to_openai_function(function_model)
|
||||
for function_model in function_model_list
|
||||
]
|
||||
|
||||
|
||||
def resolve_schema(schema, components):
|
||||
"""
|
||||
Recursively resolves a JSON schema using OpenAPI components.
|
||||
"""
|
||||
if not schema:
|
||||
return {}
|
||||
|
||||
if "$ref" in schema:
|
||||
ref_path = schema["$ref"]
|
||||
ref_parts = ref_path.strip("#/").split("/")
|
||||
resolved = components
|
||||
for part in ref_parts[1:]: # Skip the initial 'components'
|
||||
resolved = resolved.get(part, {})
|
||||
return resolve_schema(resolved, components)
|
||||
|
||||
resolved_schema = copy.deepcopy(schema)
|
||||
|
||||
# Recursively resolve inner schemas
|
||||
if "properties" in resolved_schema:
|
||||
for prop, prop_schema in resolved_schema["properties"].items():
|
||||
resolved_schema["properties"][prop] = resolve_schema(
|
||||
prop_schema, components
|
||||
)
|
||||
|
||||
if "items" in resolved_schema:
|
||||
resolved_schema["items"] = resolve_schema(resolved_schema["items"], components)
|
||||
|
||||
return resolved_schema
|
||||
|
||||
|
||||
def convert_openapi_to_tool_payload(openapi_spec):
|
||||
"""
|
||||
Converts an OpenAPI specification into a custom tool payload structure.
|
||||
|
||||
Args:
|
||||
openapi_spec (dict): The OpenAPI specification as a Python dict.
|
||||
|
||||
Returns:
|
||||
list: A list of tool payloads.
|
||||
"""
|
||||
tool_payload = []
|
||||
|
||||
for path, methods in openapi_spec.get("paths", {}).items():
|
||||
for method, operation in methods.items():
|
||||
tool = {
|
||||
"type": "function",
|
||||
"name": operation.get("operationId"),
|
||||
"description": operation.get(
|
||||
"description", operation.get("summary", "No description available.")
|
||||
),
|
||||
"parameters": {"type": "object", "properties": {}, "required": []},
|
||||
}
|
||||
|
||||
# Extract path and query parameters
|
||||
for param in operation.get("parameters", []):
|
||||
param_name = param["name"]
|
||||
param_schema = param.get("schema", {})
|
||||
tool["parameters"]["properties"][param_name] = {
|
||||
"type": param_schema.get("type"),
|
||||
"description": param_schema.get("description", ""),
|
||||
}
|
||||
if param.get("required"):
|
||||
tool["parameters"]["required"].append(param_name)
|
||||
|
||||
# Extract and resolve requestBody if available
|
||||
request_body = operation.get("requestBody")
|
||||
if request_body:
|
||||
content = request_body.get("content", {})
|
||||
json_schema = content.get("application/json", {}).get("schema")
|
||||
if json_schema:
|
||||
resolved_schema = resolve_schema(
|
||||
json_schema, openapi_spec.get("components", {})
|
||||
)
|
||||
|
||||
if resolved_schema.get("properties"):
|
||||
tool["parameters"]["properties"].update(
|
||||
resolved_schema["properties"]
|
||||
)
|
||||
if "required" in resolved_schema:
|
||||
tool["parameters"]["required"] = list(
|
||||
set(
|
||||
tool["parameters"]["required"]
|
||||
+ resolved_schema["required"]
|
||||
)
|
||||
)
|
||||
elif resolved_schema.get("type") == "array":
|
||||
tool["parameters"] = resolved_schema # special case for array
|
||||
|
||||
tool_payload.append(tool)
|
||||
|
||||
return tool_payload
|
||||
|
||||
|
||||
async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]:
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
error = None
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers) as response:
|
||||
if response.status != 200:
|
||||
error_body = await response.json()
|
||||
raise Exception(error_body)
|
||||
res = await response.json()
|
||||
except Exception as err:
|
||||
print("Error:", err)
|
||||
if isinstance(err, dict) and "detail" in err:
|
||||
error = err["detail"]
|
||||
else:
|
||||
error = str(err)
|
||||
raise Exception(error)
|
||||
|
||||
data = {
|
||||
"openapi": res,
|
||||
"info": res.get("info", {}),
|
||||
"specs": convert_openapi_to_tool_payload(res),
|
||||
}
|
||||
|
||||
print("Fetched data:", data)
|
||||
return data
|
||||
|
||||
|
||||
async def get_tool_servers_data(
|
||||
servers: List[Dict[str, Any]], session_token: Optional[str] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
# Prepare list of enabled servers along with their original index
|
||||
server_entries = []
|
||||
for idx, server in enumerate(servers):
|
||||
if server.get("config", {}).get("enable"):
|
||||
url_path = server.get("path", "openapi.json")
|
||||
full_url = f"{server.get('url')}/{url_path}"
|
||||
|
||||
auth_type = server.get("auth_type", "bearer")
|
||||
token = None
|
||||
|
||||
if auth_type == "bearer":
|
||||
token = server.get("key", "")
|
||||
elif auth_type == "session":
|
||||
token = session_token
|
||||
server_entries.append((idx, server, full_url, token))
|
||||
|
||||
# Create async tasks to fetch data
|
||||
tasks = [get_tool_server_data(token, url) for (_, _, url, token) in server_entries]
|
||||
|
||||
# Execute tasks concurrently
|
||||
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Build final results with index and server metadata
|
||||
results = []
|
||||
for (idx, server, url, _), response in zip(server_entries, responses):
|
||||
if isinstance(response, Exception):
|
||||
print(f"Failed to connect to {url} OpenAPI tool server")
|
||||
continue
|
||||
|
||||
results.append(
|
||||
{
|
||||
"idx": idx,
|
||||
"url": server.get("url"),
|
||||
"openapi": response.get("openapi"),
|
||||
"info": response.get("info"),
|
||||
"specs": response.get("specs"),
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
async def execute_tool_server(
|
||||
token: str, url: str, name: str, params: Dict[str, Any], server_data: Dict[str, Any]
|
||||
) -> Any:
|
||||
error = None
|
||||
try:
|
||||
openapi = server_data.get("openapi", {})
|
||||
paths = openapi.get("paths", {})
|
||||
|
||||
matching_route = None
|
||||
for route_path, methods in paths.items():
|
||||
for http_method, operation in methods.items():
|
||||
if isinstance(operation, dict) and operation.get("operationId") == name:
|
||||
matching_route = (route_path, methods)
|
||||
break
|
||||
if matching_route:
|
||||
break
|
||||
|
||||
if not matching_route:
|
||||
raise Exception(f"No matching route found for operationId: {name}")
|
||||
|
||||
route_path, methods = matching_route
|
||||
|
||||
method_entry = None
|
||||
for http_method, operation in methods.items():
|
||||
if operation.get("operationId") == name:
|
||||
method_entry = (http_method.lower(), operation)
|
||||
break
|
||||
|
||||
if not method_entry:
|
||||
raise Exception(f"No matching method found for operationId: {name}")
|
||||
|
||||
http_method, operation = method_entry
|
||||
|
||||
path_params = {}
|
||||
query_params = {}
|
||||
body_params = {}
|
||||
|
||||
for param in operation.get("parameters", []):
|
||||
param_name = param["name"]
|
||||
param_in = param["in"]
|
||||
if param_name in params:
|
||||
if param_in == "path":
|
||||
path_params[param_name] = params[param_name]
|
||||
elif param_in == "query":
|
||||
query_params[param_name] = params[param_name]
|
||||
|
||||
final_url = f"{url}{route_path}"
|
||||
for key, value in path_params.items():
|
||||
final_url = final_url.replace(f"{{{key}}}", str(value))
|
||||
|
||||
if query_params:
|
||||
query_string = "&".join(f"{k}={v}" for k, v in query_params.items())
|
||||
final_url = f"{final_url}?{query_string}"
|
||||
|
||||
if operation.get("requestBody", {}).get("content"):
|
||||
if params:
|
||||
body_params = params
|
||||
else:
|
||||
raise Exception(
|
||||
f"Request body expected for operation '{name}' but none found."
|
||||
)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
request_method = getattr(session, http_method.lower())
|
||||
|
||||
if http_method in ["post", "put", "patch"]:
|
||||
async with request_method(
|
||||
final_url, json=body_params, headers=headers
|
||||
) as response:
|
||||
if response.status >= 400:
|
||||
text = await response.text()
|
||||
raise Exception(f"HTTP error {response.status}: {text}")
|
||||
return await response.json()
|
||||
else:
|
||||
async with request_method(final_url, headers=headers) as response:
|
||||
if response.status >= 400:
|
||||
text = await response.text()
|
||||
raise Exception(f"HTTP error {response.status}: {text}")
|
||||
return await response.json()
|
||||
|
||||
except Exception as err:
|
||||
error = str(err)
|
||||
print("API Request Error:", error)
|
||||
return {"error": error}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
fastapi==0.115.7
|
||||
uvicorn[standard]==0.34.0
|
||||
pydantic==2.10.6
|
||||
python-multipart==0.0.18
|
||||
python-multipart==0.0.20
|
||||
|
||||
python-socketio==5.11.3
|
||||
python-jose==3.4.0
|
||||
@@ -54,8 +54,9 @@ elasticsearch==8.17.1
|
||||
|
||||
transformers
|
||||
sentence-transformers==3.3.1
|
||||
accelerate
|
||||
colbert-ai==0.2.21
|
||||
einops==0.8.0
|
||||
einops==0.8.1
|
||||
|
||||
|
||||
ftfy==6.2.3
|
||||
@@ -67,7 +68,7 @@ python-pptx==1.0.0
|
||||
unstructured==0.16.17
|
||||
nltk==3.9.1
|
||||
Markdown==3.7
|
||||
pypandoc==1.13
|
||||
pypandoc==1.15
|
||||
pandas==2.2.3
|
||||
openpyxl==3.1.5
|
||||
pyxlsb==1.0.10
|
||||
@@ -83,6 +84,8 @@ opencv-python-headless==4.11.0.86
|
||||
rapidocr-onnxruntime==1.3.24
|
||||
rank-bm25==0.2.2
|
||||
|
||||
onnxruntime==1.20.1
|
||||
|
||||
faster-whisper==1.1.1
|
||||
|
||||
PyJWT[crypto]==2.10.1
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-webui",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.2",
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^4.5.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open-webui",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run pyodide:fetch && vite dev --host",
|
||||
|
||||
+6
-3
@@ -45,7 +45,7 @@ dependencies = [
|
||||
|
||||
"openai",
|
||||
"anthropic",
|
||||
"google-generativeai==0.7.2",
|
||||
"google-generativeai==0.8.4",
|
||||
"tiktoken",
|
||||
|
||||
"langchain==0.3.19",
|
||||
@@ -61,8 +61,9 @@ dependencies = [
|
||||
|
||||
"transformers",
|
||||
"sentence-transformers==3.3.1",
|
||||
"accelerate",
|
||||
"colbert-ai==0.2.21",
|
||||
"einops==0.8.0",
|
||||
"einops==0.8.1",
|
||||
|
||||
"ftfy==6.2.3",
|
||||
"pypdf==4.3.1",
|
||||
@@ -73,7 +74,7 @@ dependencies = [
|
||||
"unstructured==0.16.17",
|
||||
"nltk==3.9.1",
|
||||
"Markdown==3.7",
|
||||
"pypandoc==1.13",
|
||||
"pypandoc==1.15",
|
||||
"pandas==2.2.3",
|
||||
"openpyxl==3.1.5",
|
||||
"pyxlsb==1.0.10",
|
||||
@@ -89,6 +90,8 @@ dependencies = [
|
||||
"rapidocr-onnxruntime==1.3.24",
|
||||
"rank-bm25==0.2.2",
|
||||
|
||||
"onnxruntime==1.20.1",
|
||||
|
||||
"faster-whisper==1.1.1",
|
||||
|
||||
"PyJWT[crypto]==2.10.1",
|
||||
|
||||
@@ -46,6 +46,14 @@ math {
|
||||
@apply rounded-lg;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
direction: auto;
|
||||
}
|
||||
|
||||
textarea::placeholder {
|
||||
direction: auto;
|
||||
}
|
||||
|
||||
.input-prose {
|
||||
@apply prose dark:prose-invert prose-headings:font-semibold prose-hr:my-4 prose-hr:border-gray-100 prose-hr:dark:border-gray-800 prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line;
|
||||
}
|
||||
|
||||
@@ -115,6 +115,93 @@ export const setDirectConnectionsConfig = async (token: string, config: object)
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getToolServerConnections = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const setToolServerConnections = async (token: string, connections: object) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...connections
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const verifyToolServerConnection = async (token: string, connection: object) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/tool_servers/verify`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...connection
|
||||
})
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
error = err.detail;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getCodeExecutionConfig = async (token: string) => {
|
||||
let error = null;
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ export const stopTask = async (token: string, id: string) => {
|
||||
export const getToolServerData = async (token: string, url: string) => {
|
||||
let error = null;
|
||||
|
||||
const res = await fetch(`${url}/openapi.json`, {
|
||||
const res = await fetch(`${url}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
@@ -304,10 +304,13 @@ export const getToolServersData = async (i18n, servers: object[]) => {
|
||||
servers
|
||||
.filter((server) => server?.config?.enable)
|
||||
.map(async (server) => {
|
||||
const data = await getToolServerData(server?.key, server?.url).catch((err) => {
|
||||
const data = await getToolServerData(
|
||||
server?.key,
|
||||
server?.url + '/' + (server?.path ?? 'openapi.json')
|
||||
).catch((err) => {
|
||||
toast.error(
|
||||
i18n.t(`Failed to connect to {{URL}} OpenAPI tool server`, {
|
||||
URL: server?.url
|
||||
URL: server?.url + '/' + (server?.path ?? 'openapi.json')
|
||||
})
|
||||
);
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Tags from './common/Tags.svelte';
|
||||
import { getToolServerData } from '$lib/apis';
|
||||
import { verifyToolServerConnection } from '$lib/apis/configs';
|
||||
import AccessControl from './workspace/common/AccessControl.svelte';
|
||||
|
||||
export let onSubmit: Function = () => {};
|
||||
export let onDelete: Function = () => {};
|
||||
@@ -22,14 +25,66 @@
|
||||
export let show = false;
|
||||
export let edit = false;
|
||||
|
||||
export let direct = false;
|
||||
|
||||
export let connection = null;
|
||||
|
||||
let url = '';
|
||||
let path = 'openapi.json';
|
||||
|
||||
let auth_type = 'bearer';
|
||||
let key = '';
|
||||
|
||||
let accessControl = null;
|
||||
|
||||
let enable = true;
|
||||
|
||||
let loading = false;
|
||||
|
||||
const verifyHandler = async () => {
|
||||
if (url === '') {
|
||||
toast.error($i18n.t('Please enter a valid URL'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (path === '') {
|
||||
toast.error($i18n.t('Please enter a valid path'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (direct) {
|
||||
const res = await getToolServerData(
|
||||
auth_type === 'bearer' ? key : localStorage.token,
|
||||
`${url}/${path}`
|
||||
).catch((err) => {
|
||||
toast.error($i18n.t('Connection failed'));
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Connection successful'));
|
||||
console.debug('Connection successful', res);
|
||||
}
|
||||
} else {
|
||||
const res = await verifyToolServerConnection(localStorage.token, {
|
||||
url,
|
||||
path,
|
||||
auth_type,
|
||||
key,
|
||||
config: {
|
||||
enable: enable,
|
||||
access_control: accessControl
|
||||
}
|
||||
}).catch((err) => {
|
||||
toast.error($i18n.t('Connection failed'));
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Connection successful'));
|
||||
console.debug('Connection successful', res);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
loading = true;
|
||||
|
||||
@@ -38,9 +93,12 @@
|
||||
|
||||
const connection = {
|
||||
url,
|
||||
path,
|
||||
auth_type,
|
||||
key,
|
||||
config: {
|
||||
enable: enable
|
||||
enable: enable,
|
||||
access_control: accessControl
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,16 +108,24 @@
|
||||
show = false;
|
||||
|
||||
url = '';
|
||||
path = 'openapi.json';
|
||||
key = '';
|
||||
auth_type = 'bearer';
|
||||
|
||||
enable = true;
|
||||
accessControl = null;
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
if (connection) {
|
||||
url = connection.url;
|
||||
key = connection.key;
|
||||
path = connection?.path ?? 'openapi.json';
|
||||
|
||||
auth_type = connection?.auth_type ?? 'bearer';
|
||||
key = connection?.key ?? '';
|
||||
|
||||
enable = connection.config?.enable ?? true;
|
||||
accessControl = connection.config?.access_control ?? null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,47 +179,113 @@
|
||||
<div class="px-1">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('URL')}</div>
|
||||
<div class="flex justify-between mb-0.5">
|
||||
<div class=" text-xs text-gray-500">{$i18n.t('URL')}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-1 items-center">
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
class="w-full flex-1 text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={url}
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col shrink-0 self-end">
|
||||
<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
|
||||
<Switch bind:state={enable} />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={$i18n.t('Verify Connection')}
|
||||
className="shrink-0 flex items-center mr-1"
|
||||
>
|
||||
<button
|
||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
|
||||
on:click={() => {
|
||||
verifyHandler();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
|
||||
<Switch bind:state={enable} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex items-center">
|
||||
<div class="text-sm">/</div>
|
||||
<input
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
type="text"
|
||||
bind:value={path}
|
||||
placeholder={$i18n.t('openapi.json Path')}
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
{$i18n.t(`WebUI will make requests to "{{url}}/openapi.json"`, {
|
||||
url: url
|
||||
{$i18n.t(`WebUI will make requests to "{{url}}"`, {
|
||||
url: `${url}/${path}`
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Key')}</div>
|
||||
<div class=" text-xs text-gray-500">{$i18n.t('Auth')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<SensitiveInput
|
||||
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={key}
|
||||
placeholder={$i18n.t('API Key')}
|
||||
required={false}
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-shrink-0 self-start">
|
||||
<select
|
||||
class="w-full text-sm bg-transparent dark:bg-gray-900 placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden pr-5"
|
||||
bind:value={auth_type}
|
||||
>
|
||||
<option value="bearer">Bearer</option>
|
||||
<option value="session">Session</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center">
|
||||
{#if auth_type === 'bearer'}
|
||||
<SensitiveInput
|
||||
className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={key}
|
||||
placeholder={$i18n.t('API Key')}
|
||||
required={false}
|
||||
/>
|
||||
{:else if auth_type === 'session'}
|
||||
<div class="text-xs text-gray-500 self-center translate-y-[1px]">
|
||||
{$i18n.t('Forwards system user session credentials to authenticate')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !direct}
|
||||
<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="my-2 -mx-2">
|
||||
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
||||
<AccessControl bind:accessControl />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<span class="text-lg font-medium text-gray-500 dark:text-gray-300">{feedbacks.length}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{#if feedbacks.length > 0}
|
||||
<div>
|
||||
<Tooltip content={$i18n.t('Export')}>
|
||||
<button
|
||||
@@ -128,7 +128,7 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -430,39 +430,41 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
const _functions = await exportFunctions(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (_functions) {
|
||||
let blob = new Blob([JSON.stringify(_functions)], {
|
||||
type: 'application/json'
|
||||
{#if $functions.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
const _functions = await exportFunctions(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
saveAs(blob, `functions-export-${Date.now()}.json`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Functions')}</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
if (_functions) {
|
||||
let blob = new Blob([JSON.stringify(_functions)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `functions-export-${Date.now()}.json`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Functions')}</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import DocumentChartBar from '../icons/DocumentChartBar.svelte';
|
||||
import Evaluations from './Settings/Evaluations.svelte';
|
||||
import CodeExecution from './Settings/CodeExecution.svelte';
|
||||
import Tools from './Settings/Tools.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -135,6 +136,32 @@
|
||||
<div class=" self-center">{$i18n.t('Evaluations')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
|
||||
'tools'
|
||||
? ''
|
||||
: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
|
||||
on:click={() => {
|
||||
selectedTab = 'tools';
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center">{$i18n.t('Tools')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
|
||||
'documents'
|
||||
@@ -373,6 +400,8 @@
|
||||
<Models />
|
||||
{:else if selectedTab === 'evaluations'}
|
||||
<Evaluations />
|
||||
{:else if selectedTab === 'tools'}
|
||||
<Tools />
|
||||
{:else if selectedTab === 'documents'}
|
||||
<Documents
|
||||
on:save={async () => {
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if ($user.role === 'admin') {
|
||||
if ($user?.role === 'admin') {
|
||||
let ollamaConfig = {};
|
||||
let openaiConfig = {};
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
let documentIntelligenceEndpoint = '';
|
||||
let documentIntelligenceKey = '';
|
||||
let showDocumentIntelligenceConfig = false;
|
||||
let mistralApiKey = '';
|
||||
let showMistralOcrConfig = false;
|
||||
|
||||
let textSplitter = '';
|
||||
let chunkSize = 0;
|
||||
@@ -189,6 +191,10 @@
|
||||
toast.error($i18n.t('Document Intelligence endpoint and key required.'));
|
||||
return;
|
||||
}
|
||||
if (contentExtractionEngine === 'mistral_ocr' && mistralApiKey === '') {
|
||||
toast.error($i18n.t('Mistral OCR API Key required.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BYPASS_EMBEDDING_AND_RETRIEVAL) {
|
||||
await embeddingModelUpdateHandler();
|
||||
@@ -220,6 +226,9 @@
|
||||
document_intelligence_config: {
|
||||
key: documentIntelligenceKey,
|
||||
endpoint: documentIntelligenceEndpoint
|
||||
},
|
||||
mistral_ocr_config: {
|
||||
api_key: mistralApiKey
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -284,6 +293,8 @@
|
||||
documentIntelligenceEndpoint = res.content_extraction.document_intelligence_config.endpoint;
|
||||
documentIntelligenceKey = res.content_extraction.document_intelligence_config.key;
|
||||
showDocumentIntelligenceConfig = contentExtractionEngine === 'document_intelligence';
|
||||
mistralApiKey = res.content_extraction.mistral_ocr_config.api_key;
|
||||
showMistralOcrConfig = contentExtractionEngine === 'mistral_ocr';
|
||||
|
||||
fileMaxSize = res?.file.max_size ?? '';
|
||||
fileMaxCount = res?.file.max_count ?? '';
|
||||
@@ -335,21 +346,21 @@
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class=" mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
<div class="self-center text-xs font-medium">
|
||||
{$i18n.t('Content Extraction Engine')}
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<select
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
|
||||
bind:value={contentExtractionEngine}
|
||||
>
|
||||
<option value="">{$i18n.t('Default')} </option>
|
||||
<option value="">{$i18n.t('Default')}</option>
|
||||
<option value="tika">{$i18n.t('Tika')}</option>
|
||||
<option value="docling">{$i18n.t('Docling')}</option>
|
||||
<option value="document_intelligence">{$i18n.t('Document Intelligence')}</option>
|
||||
<option value="mistral_ocr">{$i18n.t('Mistral OCR')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -378,12 +389,18 @@
|
||||
placeholder={$i18n.t('Enter Document Intelligence Endpoint')}
|
||||
bind:value={documentIntelligenceEndpoint}
|
||||
/>
|
||||
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Document Intelligence Key')}
|
||||
bind:value={documentIntelligenceKey}
|
||||
/>
|
||||
</div>
|
||||
{:else if contentExtractionEngine === 'mistral_ocr'}
|
||||
<div class="my-0.5 flex gap-2 pr-2">
|
||||
<SensitiveInput
|
||||
placeholder={$i18n.t('Enter Mistral API Key')}
|
||||
bind:value={mistralApiKey}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if ($user.role === 'admin') {
|
||||
if ($user?.role === 'admin') {
|
||||
evaluationConfig = await getConfig(localStorage.token).catch((err) => {
|
||||
toast.error(err);
|
||||
return null;
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
if ($user.role === 'admin') {
|
||||
if ($user?.role === 'admin') {
|
||||
const res = await getConfig(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
|
||||
@@ -380,7 +380,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user.role === 'admin'}
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class=" space-y-3">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-sm font-semibold">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
let ollamaConfig = null;
|
||||
|
||||
onMount(async () => {
|
||||
if ($user.role === 'admin') {
|
||||
if ($user?.role === 'admin') {
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
ollamaConfig = await getOllamaConfig(localStorage.token);
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
|
||||
import { getModels as _getModels } from '$lib/apis';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { models, settings, user } from '$lib/stores';
|
||||
|
||||
import Switch from '$lib/components/common/Switch.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Connection from '$lib/components/chat/Settings/Tools/Connection.svelte';
|
||||
|
||||
import AddServerModal from '$lib/components/AddServerModal.svelte';
|
||||
import { getToolServerConnections, setToolServerConnections } from '$lib/apis/configs';
|
||||
|
||||
export let saveSettings: Function;
|
||||
|
||||
let servers = null;
|
||||
let showConnectionModal = false;
|
||||
|
||||
const addConnectionHandler = async (server) => {
|
||||
servers = [...servers, server];
|
||||
await updateHandler();
|
||||
};
|
||||
|
||||
const updateHandler = async () => {
|
||||
const res = await setToolServerConnections(localStorage.token, {
|
||||
TOOL_SERVER_CONNECTIONS: servers
|
||||
}).catch((err) => {
|
||||
toast.error($i18n.t('Failed to save connections'));
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Connections saved successfully'));
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const res = await getToolServerConnections(localStorage.token);
|
||||
servers = res.TOOL_SERVER_CONNECTIONS;
|
||||
});
|
||||
</script>
|
||||
|
||||
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between text-sm"
|
||||
on:submit|preventDefault={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
>
|
||||
<div class=" overflow-y-scroll scrollbar-hidden h-full">
|
||||
{#if servers !== null}
|
||||
<div class="">
|
||||
<div class="mb-3">
|
||||
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
|
||||
|
||||
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="mb-2.5 flex flex-col w-full justify-between">
|
||||
<!-- {$i18n.t(`Failed to connect to {{URL}} OpenAPI tool server`, {
|
||||
URL: 'server?.url'
|
||||
})} -->
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<div class="font-medium">{$i18n.t('Manage Tool Servers')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Add Connection`)}>
|
||||
<button
|
||||
class="px-1"
|
||||
on:click={() => {
|
||||
showConnectionModal = true;
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<Plus />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each servers as server, idx}
|
||||
<Connection
|
||||
bind:connection={server}
|
||||
onSubmit={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
onDelete={() => {
|
||||
servers = servers.filter((_, i) => i !== idx);
|
||||
updateHandler();
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="my-1.5">
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="mb-2.5 flex w-full justify-between">
|
||||
<div class=" text-xs font-medium">{$i18n.t('Arena Models')}</div>
|
||||
|
||||
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
|
||||
<Switch bind:state={evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS} />
|
||||
</Tooltip>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex h-full justify-center">
|
||||
<div class="my-auto">
|
||||
<Spinner className="size-6" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||
<button
|
||||
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
||||
type="submit"
|
||||
>
|
||||
{$i18n.t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -64,9 +64,10 @@
|
||||
delete: true,
|
||||
edit: true,
|
||||
temporary: true,
|
||||
temporary_enforced: true
|
||||
temporary_enforced: false
|
||||
},
|
||||
features: {
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
prompts: false,
|
||||
tools: false
|
||||
},
|
||||
sharing: {
|
||||
public_models: false,
|
||||
public_knowledge: false,
|
||||
public_prompts: false,
|
||||
public_tools: false
|
||||
},
|
||||
chat: {
|
||||
controls: true,
|
||||
file_upload: true,
|
||||
@@ -46,6 +52,7 @@
|
||||
temporary: true
|
||||
},
|
||||
features: {
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
|
||||
@@ -25,9 +25,10 @@
|
||||
edit: true,
|
||||
file_upload: true,
|
||||
temporary: true,
|
||||
temporary_enforced: true
|
||||
temporary_enforced: false
|
||||
},
|
||||
features: {
|
||||
direct_tool_servers: false,
|
||||
web_search: true,
|
||||
image_generation: true,
|
||||
code_interpreter: true
|
||||
@@ -295,6 +296,14 @@
|
||||
<div>
|
||||
<div class=" mb-2 text-sm font-medium">{$i18n.t('Features Permissions')}</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Direct Tool Servers')}
|
||||
</div>
|
||||
|
||||
<Switch bind:state={permissions.features.direct_tool_servers} />
|
||||
</div>
|
||||
|
||||
<div class=" flex w-full justify-between my-2 pr-2">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Web Search')}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
messages[idx] = data;
|
||||
}
|
||||
} else if (type === 'typing' && event.message_id === null) {
|
||||
if (event.user.id === $user.id) {
|
||||
if (event.user.id === $user?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@
|
||||
>
|
||||
<div
|
||||
class="flex-1 flex flex-col relative w-full rounded-3xl px-1 bg-gray-600/5 dark:bg-gray-400/5 dark:text-gray-100"
|
||||
dir={$settings?.chatDirection ?? 'LTR'}
|
||||
dir={$settings?.chatDirection ?? 'auto'}
|
||||
>
|
||||
{#if files.length > 0}
|
||||
<div class="mx-2 mt-2.5 -mb-1 flex flex-wrap gap-2">
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
if (
|
||||
(message?.reactions ?? [])
|
||||
.find((reaction) => reaction.name === name)
|
||||
?.user_ids?.includes($user.id) ??
|
||||
?.user_ids?.includes($user?.id) ??
|
||||
false
|
||||
) {
|
||||
messages = messages.map((m) => {
|
||||
@@ -140,7 +140,7 @@
|
||||
const reaction = m.reactions.find((reaction) => reaction.name === name);
|
||||
|
||||
if (reaction) {
|
||||
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user.id);
|
||||
reaction.user_ids = reaction.user_ids.filter((id) => id !== $user?.id);
|
||||
reaction.count = reaction.user_ids.length;
|
||||
|
||||
if (reaction.count === 0) {
|
||||
@@ -167,12 +167,12 @@
|
||||
const reaction = m.reactions.find((reaction) => reaction.name === name);
|
||||
|
||||
if (reaction) {
|
||||
reaction.user_ids.push($user.id);
|
||||
reaction.user_ids.push($user?.id);
|
||||
reaction.count = reaction.user_ids.length;
|
||||
} else {
|
||||
m.reactions.push({
|
||||
name: name,
|
||||
user_ids: [$user.id],
|
||||
user_ids: [$user?.id],
|
||||
count: 1
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if message.user_id === $user.id || $user.role === 'admin'}
|
||||
{#if message.user_id === $user?.id || $user?.role === 'admin'}
|
||||
<Tooltip content={$i18n.t('Edit')}>
|
||||
<button
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-800 transition rounded-lg p-1"
|
||||
@@ -265,7 +265,7 @@
|
||||
<Tooltip content={`:${reaction.name}:`}>
|
||||
<button
|
||||
class="flex items-center gap-1.5 transition rounded-xl px-2 py-1 cursor-pointer {reaction.user_ids.includes(
|
||||
$user.id
|
||||
$user?.id
|
||||
)
|
||||
? ' bg-blue-300/10 outline outline-blue-500/50 outline-1'
|
||||
: 'bg-gray-300/10 dark:bg-gray-500/10 hover:outline hover:outline-gray-700/30 dark:hover:outline-gray-300/30 hover:outline-1'}"
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
{#if $user !== undefined}
|
||||
<UserMenu
|
||||
className="max-w-[200px]"
|
||||
role={$user.role}
|
||||
role={$user?.role}
|
||||
on:show={(e) => {
|
||||
if (e.detail === 'archived-chat') {
|
||||
showArchivedChats.set(true);
|
||||
@@ -71,7 +71,7 @@
|
||||
>
|
||||
<div class=" self-center">
|
||||
<img
|
||||
src={$user.profile_image_url}
|
||||
src={$user?.profile_image_url}
|
||||
class="size-6 object-cover rounded-full"
|
||||
alt="User profile"
|
||||
draggable="false"
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
}
|
||||
} else if (type === 'typing' && event.message_id === threadId) {
|
||||
if (event.user.id === $user.id) {
|
||||
if (event.user.id === $user?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
splitStream,
|
||||
sleep,
|
||||
removeDetails,
|
||||
getPromptVariables
|
||||
getPromptVariables,
|
||||
processDetails
|
||||
} from '$lib/utils';
|
||||
|
||||
import { generateChatCompletion } from '$lib/apis/ollama';
|
||||
@@ -1336,6 +1337,10 @@
|
||||
parentId: string,
|
||||
{ modelId = null, modelIdx = null, newChat = false } = {}
|
||||
) => {
|
||||
if (autoScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
let _chatId = JSON.parse(JSON.stringify($chatId));
|
||||
_history = JSON.parse(JSON.stringify(_history));
|
||||
|
||||
@@ -1498,7 +1503,7 @@
|
||||
role: 'system',
|
||||
content: `${promptTemplate(
|
||||
params?.system ?? $settings?.system ?? '',
|
||||
$user.name,
|
||||
$user?.name,
|
||||
$settings?.userLocation
|
||||
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
|
||||
console.error(err);
|
||||
@@ -1514,7 +1519,7 @@
|
||||
: undefined,
|
||||
...createMessagesList(_history, responseMessageId).map((message) => ({
|
||||
...message,
|
||||
content: removeDetails(message.content, ['reasoning', 'code_interpreter'])
|
||||
content: processDetails(message.content)
|
||||
}))
|
||||
].filter((message) => message);
|
||||
|
||||
@@ -1572,23 +1577,23 @@
|
||||
features: {
|
||||
image_generation:
|
||||
$config?.features?.enable_image_generation &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.image_generation)
|
||||
($user?.role === 'admin' || $user?.permissions?.features?.image_generation)
|
||||
? imageGenerationEnabled
|
||||
: false,
|
||||
code_interpreter:
|
||||
$config?.features?.enable_code_interpreter &&
|
||||
($user.role === 'admin' || $user?.permissions?.features?.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)
|
||||
($user?.role === 'admin' || $user?.permissions?.features?.web_search)
|
||||
? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
|
||||
: false
|
||||
},
|
||||
variables: {
|
||||
...getPromptVariables(
|
||||
$user.name,
|
||||
$user?.name,
|
||||
$settings?.userLocation
|
||||
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
|
||||
console.error(err);
|
||||
@@ -1739,6 +1744,11 @@
|
||||
history.currentId = userMessageId;
|
||||
|
||||
await tick();
|
||||
|
||||
if (autoScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
await sendPrompt(history, userPrompt, userMessageId);
|
||||
};
|
||||
|
||||
@@ -1749,6 +1759,10 @@
|
||||
let userMessage = history.messages[message.parentId];
|
||||
let userPrompt = userMessage.content;
|
||||
|
||||
if (autoScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
if ((userMessage?.models ?? [...selectedModels]).length == 1) {
|
||||
// If user message has only one model selected, sendPrompt automatically selects it for regeneration
|
||||
await sendPrompt(history, userPrompt, userMessage.id);
|
||||
|
||||
@@ -68,12 +68,12 @@
|
||||
|
||||
{#if $temporaryChatEnabled}
|
||||
<Tooltip
|
||||
content="This chat won't appear in history and your messages will not be saved."
|
||||
className="w-fit"
|
||||
placement="top-start"
|
||||
content={$i18n.t('This chat won’t appear in history and your messages will not be saved.')}
|
||||
className="w-full flex justify-center mb-0.5"
|
||||
placement="top"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
|
||||
<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
|
||||
<EyeSlash strokeWidth="2.5" className="size-5" />{$i18n.t('Temporary Chat')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
@@ -86,7 +86,7 @@
|
||||
{#if models[selectedModelIdx]?.name}
|
||||
{models[selectedModelIdx]?.name}
|
||||
{:else}
|
||||
{$i18n.t('Hello, {{name}}', { name: $user.name })}
|
||||
{$i18n.t('Hello, {{name}}', { name: $user?.name })}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
</Collapsible>
|
||||
|
||||
{#if $user.role === 'admin' || $user?.permissions.chat?.controls}
|
||||
{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
|
||||
<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
|
||||
|
||||
<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full">
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
TTSWorker
|
||||
} from '$lib/stores';
|
||||
|
||||
import { blobToFile, compressImage, createMessagesList, findWordIndices } from '$lib/utils';
|
||||
import {
|
||||
blobToFile,
|
||||
compressImage,
|
||||
createMessagesList,
|
||||
extractCurlyBraceWords
|
||||
} from '$lib/utils';
|
||||
import { transcribeAudio } from '$lib/apis/audio';
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
import { generateAutoCompletion } from '$lib/apis';
|
||||
@@ -47,6 +52,7 @@
|
||||
import CommandLine from '../icons/CommandLine.svelte';
|
||||
import { KokoroWorker } from '$lib/workers/KokoroWorker';
|
||||
import ToolServersModal from './ToolServersModal.svelte';
|
||||
import Wrench from '../icons/Wrench.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -85,7 +91,7 @@
|
||||
webSearchEnabled
|
||||
});
|
||||
|
||||
let showToolServers = false;
|
||||
let showTools = false;
|
||||
|
||||
let loaded = false;
|
||||
let recording = false;
|
||||
@@ -348,7 +354,7 @@
|
||||
|
||||
<FilesOverlay show={dragged} />
|
||||
|
||||
<ToolServersModal bind:show={showToolServers} />
|
||||
<ToolServersModal bind:show={showTools} {selectedToolIds} />
|
||||
|
||||
{#if loaded}
|
||||
<div class="w-full font-primary">
|
||||
@@ -392,38 +398,6 @@
|
||||
<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-linear-to-t from-white dark:from-gray-900 z-10"
|
||||
>
|
||||
{#if selectedToolIds.length > 0}
|
||||
<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-yellow-400 opacity-75"
|
||||
/>
|
||||
<span class="relative inline-flex rounded-full size-2 bg-yellow-500" />
|
||||
</span>
|
||||
</div>
|
||||
<div class=" text-ellipsis line-clamp-1 flex">
|
||||
{#each selectedToolIds.map((id) => {
|
||||
return $tools ? $tools.find((t) => t.id === id) : { id: id, name: id };
|
||||
}) as tool, toolIdx (toolIdx)}
|
||||
<Tooltip
|
||||
content={tool?.meta?.description ?? ''}
|
||||
className=" {toolIdx !== 0 ? 'pl-0.5' : ''} shrink-0"
|
||||
placement="top"
|
||||
>
|
||||
{tool.name}
|
||||
</Tooltip>
|
||||
|
||||
{#if toolIdx !== selectedToolIds.length - 1}
|
||||
<span>, </span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if atSelectedModel !== undefined}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="pl-[1px] flex items-center gap-2 text-sm dark:text-gray-500">
|
||||
@@ -536,7 +510,7 @@
|
||||
>
|
||||
<div
|
||||
class="flex-1 flex flex-col relative w-full shadow-lg rounded-3xl border border-gray-100 dark:border-gray-850 hover:border-gray-200 focus-within:border-gray-200 hover:dark:border-gray-800 focus-within:dark:border-gray-800 transition px-1 bg-white/90 dark:bg-gray-400/5 dark:text-gray-100"
|
||||
dir={$settings?.chatDirection ?? 'LTR'}
|
||||
dir={$settings?.chatDirection ?? 'auto'}
|
||||
>
|
||||
{#if files.length > 0}
|
||||
<div class="mx-2 mt-2.5 -mb-1 flex items-center flex-wrap gap-2">
|
||||
@@ -631,6 +605,7 @@
|
||||
{#if $settings?.richTextInput ?? true}
|
||||
<div
|
||||
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
|
||||
id="chat-input-container"
|
||||
>
|
||||
<RichTextInput
|
||||
bind:this={chatInputElement}
|
||||
@@ -846,6 +821,7 @@
|
||||
{:else}
|
||||
<textarea
|
||||
id="chat-input"
|
||||
dir="auto"
|
||||
bind:this={chatInputElement}
|
||||
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none"
|
||||
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
|
||||
@@ -977,7 +953,7 @@
|
||||
}
|
||||
|
||||
if (e.key === 'Tab') {
|
||||
const words = findWordIndices(prompt);
|
||||
const words = extractCurlyBraceWords(prompt);
|
||||
|
||||
if (words.length > 0) {
|
||||
const word = words.at(0);
|
||||
@@ -1057,8 +1033,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<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%]">
|
||||
<div class=" flex justify-between mt-1.5 mb-2.5 mx-0.5 max-w-full" dir="ltr">
|
||||
<div class="ml-1 self-end flex items-center flex-1 max-w-[80%] gap-0.5">
|
||||
<InputMenu
|
||||
bind:selectedToolIds
|
||||
{screenCaptureHandler}
|
||||
@@ -1126,7 +1102,30 @@
|
||||
</button>
|
||||
</InputMenu>
|
||||
|
||||
<div class="flex gap-0.5 items-center overflow-x-auto scrollbar-none flex-1">
|
||||
<div class="flex gap-[2px] items-center overflow-x-auto scrollbar-none flex-1">
|
||||
{#if toolServers.length + selectedToolIds.length > 0}
|
||||
<Tooltip
|
||||
content={$i18n.t('{{COUNT}} Available Tools', {
|
||||
COUNT: toolServers.length + selectedToolIds.length
|
||||
})}
|
||||
>
|
||||
<button
|
||||
class="translate-y-[0.5px] flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg p-1 self-center transition"
|
||||
aria-label="Available Tools"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showTools = !showTools;
|
||||
}}
|
||||
>
|
||||
<Wrench className="size-4" strokeWidth="1.75" />
|
||||
|
||||
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
|
||||
{toolServers.length + selectedToolIds.length}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#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">
|
||||
@@ -1140,7 +1139,7 @@
|
||||
>
|
||||
<GlobeAlt className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
|
||||
>{$i18n.t('Web Search')}</span
|
||||
>
|
||||
</button>
|
||||
@@ -1159,7 +1158,7 @@
|
||||
>
|
||||
<Photo className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
|
||||
>{$i18n.t('Image')}</span
|
||||
>
|
||||
</button>
|
||||
@@ -1178,7 +1177,7 @@
|
||||
>
|
||||
<CommandLine className="size-5" strokeWidth="1.75" />
|
||||
<span
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px] mr-0.5"
|
||||
class="hidden @xl:block whitespace-nowrap overflow-hidden text-ellipsis translate-y-[0.5px]"
|
||||
>{$i18n.t('Code Interpreter')}</span
|
||||
>
|
||||
</button>
|
||||
@@ -1189,47 +1188,6 @@
|
||||
</div>
|
||||
|
||||
<div class="self-end flex space-x-1 mr-1 shrink-0">
|
||||
{#if toolServers.length > 0}
|
||||
<Tooltip
|
||||
content={$i18n.t('{{COUNT}} Available Tool Servers', {
|
||||
COUNT: toolServers.length
|
||||
})}
|
||||
>
|
||||
<button
|
||||
class="translate-y-[1.5px] flex gap-1 items-center text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 rounded-lg px-1.5 py-0.5 mr-0.5 self-center border border-gray-100 dark:border-gray-800 transition"
|
||||
aria-label="Available Tool Servers"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showToolServers = !showToolServers;
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-3"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M21.75 6.75a4.5 4.5 0 0 1-4.884 4.484c-1.076-.091-2.264.071-2.95.904l-7.152 8.684a2.548 2.548 0 1 1-3.586-3.586l8.684-7.152c.833-.686.995-1.874.904-2.95a4.5 4.5 0 0 1 6.336-4.486l-3.276 3.276a3.004 3.004 0 0 0 2.25 2.25l3.276-3.276c.256.565.398 1.192.398 1.852Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M4.867 19.125h.008v.008h-.008v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span class="text-xs">
|
||||
{toolServers.length}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|
||||
<Tooltip content={$i18n.t('Record voice')}>
|
||||
<button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { prompts, settings, user } from '$lib/stores';
|
||||
import {
|
||||
findWordIndices,
|
||||
extractCurlyBraceWords,
|
||||
getUserPosition,
|
||||
getFormattedDate,
|
||||
getFormattedTime,
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
if (command.content.includes('{{USER_NAME}}')) {
|
||||
console.log($user);
|
||||
const name = $user.name || 'User';
|
||||
const name = $user?.name || 'User';
|
||||
text = text.replaceAll('{{USER_NAME}}', name);
|
||||
}
|
||||
|
||||
@@ -127,29 +127,49 @@
|
||||
const lastWord = lastLineWords.pop();
|
||||
|
||||
if ($settings?.richTextInput ?? true) {
|
||||
lastLineWords.push(`${text.replace(/</g, '<').replace(/>/g, '>')}`);
|
||||
lastLineWords.push(
|
||||
`${text.replace(/</g, '<').replace(/>/g, '>').replaceAll('\n', '<br/>')}`
|
||||
);
|
||||
|
||||
lines.push(lastLineWords.join(' '));
|
||||
prompt = lines.join('<br/>');
|
||||
} else {
|
||||
lastLineWords.push(text);
|
||||
lines.push(lastLineWords.join(' '));
|
||||
prompt = lines.join('\n');
|
||||
}
|
||||
|
||||
prompt = lines.join('\n');
|
||||
|
||||
const chatInputContainerElement = document.getElementById('chat-input-container');
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
await tick();
|
||||
if (chatInputContainerElement) {
|
||||
chatInputContainerElement.style.height = '';
|
||||
chatInputContainerElement.style.height =
|
||||
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
|
||||
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
|
||||
}
|
||||
|
||||
await tick();
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
chatInputElement.dispatchEvent(new Event('input'));
|
||||
|
||||
const words = extractCurlyBraceWords(prompt);
|
||||
|
||||
if (words.length > 0) {
|
||||
const word = words.at(0);
|
||||
const fullPrompt = prompt;
|
||||
|
||||
prompt = prompt.substring(0, word?.endIndex + 1);
|
||||
await tick();
|
||||
|
||||
chatInputElement.scrollTop = chatInputElement.scrollHeight;
|
||||
|
||||
prompt = fullPrompt;
|
||||
await tick();
|
||||
|
||||
chatInputElement.setSelectionRange(word?.startIndex, word.endIndex + 1);
|
||||
} else {
|
||||
chatInputElement.scrollTop = chatInputElement.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
let fileUploadEnabled = true;
|
||||
$: fileUploadEnabled = $user.role === 'admin' || $user?.permissions?.chat?.file_upload;
|
||||
$: fileUploadEnabled = $user?.role === 'admin' || $user?.permissions?.chat?.file_upload;
|
||||
|
||||
const init = async () => {
|
||||
if ($_tools === null) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { createEventDispatcher, tick, getContext, onMount, onDestroy } from 'svelte';
|
||||
import { config, settings } from '$lib/stores';
|
||||
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
|
||||
import { blobToFile, calculateSHA256, extractCurlyBraceWords } from '$lib/utils';
|
||||
|
||||
import { transcribeAudio } from '$lib/apis/audio';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getChatList, updateChatById } from '$lib/apis/chats';
|
||||
import { copyToClipboard, findWordIndices } from '$lib/utils';
|
||||
import { copyToClipboard, extractCurlyBraceWords } from '$lib/utils';
|
||||
|
||||
import Message from './Messages/Message.svelte';
|
||||
import Loader from '../common/Loader.svelte';
|
||||
@@ -406,19 +406,6 @@
|
||||
}
|
||||
|
||||
prompt = text;
|
||||
|
||||
await tick();
|
||||
|
||||
const chatInputContainerElement = document.getElementById('chat-input-container');
|
||||
if (chatInputContainerElement) {
|
||||
prompt = p;
|
||||
|
||||
chatInputContainerElement.style.height = '';
|
||||
chatInputContainerElement.style.height =
|
||||
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
|
||||
chatInputContainerElement.focus();
|
||||
}
|
||||
|
||||
await tick();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
let mergedDocuments = [];
|
||||
|
||||
function calculatePercentage(distance: number) {
|
||||
if (typeof distance !== 'number') return null;
|
||||
if (distance < 0) return 0;
|
||||
if (distance > 1) return 100;
|
||||
return Math.round(distance * 10000) / 100;
|
||||
@@ -122,11 +123,14 @@
|
||||
<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2 w-fit">
|
||||
{#if showPercentage}
|
||||
{@const percentage = calculatePercentage(document.distance)}
|
||||
<span
|
||||
class={`px-1 rounded-sm font-medium ${getRelevanceColor(percentage)}`}
|
||||
>
|
||||
{percentage.toFixed(2)}%
|
||||
</span>
|
||||
|
||||
{#if typeof percentage === 'number'}
|
||||
<span
|
||||
class={`px-1 rounded-sm font-medium ${getRelevanceColor(percentage)}`}
|
||||
>
|
||||
{percentage.toFixed(2)}%
|
||||
</span>
|
||||
{/if}
|
||||
<span class="text-gray-500 dark:text-gray-500">
|
||||
({(document?.distance ?? 0).toFixed(4)})
|
||||
</span>
|
||||
|
||||
@@ -184,9 +184,9 @@
|
||||
{/if}
|
||||
{:else if token.type === 'list'}
|
||||
{#if token.ordered}
|
||||
<ol start={token.start || 1}>
|
||||
<ol start={token.start || 1} dir="auto">
|
||||
{#each token.items as item, itemIdx}
|
||||
<li dir="auto" class="text-start">
|
||||
<li class="text-start">
|
||||
{#if item?.task}
|
||||
<input
|
||||
class=" translate-y-[1px] -translate-x-1"
|
||||
@@ -216,9 +216,9 @@
|
||||
{/each}
|
||||
</ol>
|
||||
{:else}
|
||||
<ul>
|
||||
<ul dir="auto">
|
||||
{#each token.items as item, itemIdx}
|
||||
<li dir="auto" class="text-start">
|
||||
<li class="text-start">
|
||||
{#if item?.task}
|
||||
<input
|
||||
class=" translate-y-[1px] -translate-x-1"
|
||||
@@ -295,7 +295,7 @@
|
||||
</p>
|
||||
{:else if token.type === 'text'}
|
||||
{#if top}
|
||||
<p dir="auto">
|
||||
<p>
|
||||
{#if token.tokens}
|
||||
<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} {onSourceClick} />
|
||||
{:else}
|
||||
|
||||
@@ -332,9 +332,37 @@
|
||||
}
|
||||
};
|
||||
|
||||
let preprocessedDetailsCache = [];
|
||||
|
||||
function preprocessForEditing(content: string): string {
|
||||
// Replace <details>...</details> with unique ID placeholder
|
||||
const detailsBlocks = [];
|
||||
let i = 0;
|
||||
|
||||
content = content.replace(/<details[\s\S]*?<\/details>/gi, (match) => {
|
||||
detailsBlocks.push(match);
|
||||
return `<details id="__DETAIL_${i++}__"/>`;
|
||||
});
|
||||
|
||||
// Store original blocks in the editedContent or globally (see merging later)
|
||||
preprocessedDetailsCache = detailsBlocks;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function postprocessAfterEditing(content: string): string {
|
||||
const restoredContent = content.replace(
|
||||
/<details id="__DETAIL_(\d+)__"\/>/g,
|
||||
(_, index) => preprocessedDetailsCache[parseInt(index)] || ''
|
||||
);
|
||||
|
||||
return restoredContent;
|
||||
}
|
||||
|
||||
const editMessageHandler = async () => {
|
||||
edit = true;
|
||||
editedContent = message.content;
|
||||
|
||||
editedContent = preprocessForEditing(message.content);
|
||||
|
||||
await tick();
|
||||
|
||||
@@ -343,7 +371,8 @@
|
||||
};
|
||||
|
||||
const editMessageConfirmHandler = async () => {
|
||||
editMessage(message.id, editedContent ? editedContent : '', false);
|
||||
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
|
||||
editMessage(message.id, messageContent, false);
|
||||
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
@@ -352,7 +381,9 @@
|
||||
};
|
||||
|
||||
const saveAsCopyHandler = async () => {
|
||||
editMessage(message.id, editedContent ? editedContent : '');
|
||||
const messageContent = postprocessAfterEditing(editedContent ? editedContent : '');
|
||||
|
||||
editMessage(message.id, messageContent);
|
||||
|
||||
edit = false;
|
||||
editedContent = '';
|
||||
@@ -551,7 +582,7 @@
|
||||
id="message-{message.id}"
|
||||
dir={$settings.chatDirection}
|
||||
>
|
||||
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
|
||||
<div class={`shrink-0 ltr:mr-3 rtl:ml-3`}>
|
||||
<ProfileImage
|
||||
src={model?.info?.meta?.profile_image_url ??
|
||||
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||
@@ -698,7 +729,7 @@
|
||||
<div>
|
||||
<button
|
||||
id="save-new-message-button"
|
||||
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
||||
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border border-gray-100 dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
||||
on:click={() => {
|
||||
saveAsCopyHandler();
|
||||
}}
|
||||
@@ -920,7 +951,7 @@
|
||||
|
||||
{#if message.done}
|
||||
{#if !readOnly}
|
||||
{#if $user.role === 'user' ? ($user?.permissions?.chat?.edit ?? true) : true}
|
||||
{#if $user?.role === 'user' ? ($user?.permissions?.chat?.edit ?? true) : true}
|
||||
<Tooltip content={$i18n.t('Edit')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
@@ -1053,7 +1084,7 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if $config?.features.enable_image_generation && ($user.role === 'admin' || $user?.permissions?.features?.image_generation) && !readOnly}
|
||||
{#if $config?.features.enable_image_generation && ($user?.role === 'admin' || $user?.permissions?.features?.image_generation) && !readOnly}
|
||||
<Tooltip content={$i18n.t('Generate Image')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
|
||||
{#if !($settings?.chatBubble ?? true)}
|
||||
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
|
||||
<div class={`shrink-0 ltr:mr-3 rtl:ml-3`}>
|
||||
<ProfileImage
|
||||
src={message.user
|
||||
? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ??
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
label: model.name,
|
||||
model: model
|
||||
}))}
|
||||
showTemporaryChatControl={$user.role === 'user'
|
||||
showTemporaryChatControl={$user?.role === 'user'
|
||||
? ($user?.permissions?.chat?.temporary ?? true) &&
|
||||
!($user?.permissions?.chat?.temporary_enforced ?? false)
|
||||
: true}
|
||||
|
||||
@@ -634,7 +634,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
|
||||
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user?.role === 'admin'}
|
||||
<Tooltip
|
||||
content={$i18n.t(`Pull "{{searchValue}}" from Ollama.com`, {
|
||||
searchValue: searchValue
|
||||
|
||||
@@ -147,10 +147,10 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if $user !== undefined}
|
||||
{#if $user !== undefined && $user !== null}
|
||||
<UserMenu
|
||||
className="max-w-[200px]"
|
||||
role={$user.role}
|
||||
role={$user?.role}
|
||||
on:show={(e) => {
|
||||
if (e.detail === 'archived-chat') {
|
||||
showArchivedChats.set(true);
|
||||
@@ -163,7 +163,7 @@
|
||||
>
|
||||
<div class=" self-center">
|
||||
<img
|
||||
src={$user.profile_image_url}
|
||||
src={$user?.profile_image_url}
|
||||
class="size-6 object-cover rounded-full"
|
||||
alt="User profile"
|
||||
draggable="false"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import { config, user, models as _models, temporaryChatEnabled } from '$lib/stores';
|
||||
import { sanitizeResponseContent, findWordIndices } from '$lib/utils';
|
||||
import { sanitizeResponseContent, extractCurlyBraceWords } from '$lib/utils';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
import Suggestions from './Suggestions.svelte';
|
||||
@@ -65,9 +65,7 @@
|
||||
const chatInputElement = document.getElementById('chat-input');
|
||||
|
||||
if (chatInputContainerElement) {
|
||||
chatInputContainerElement.style.height = '';
|
||||
chatInputContainerElement.style.height =
|
||||
Math.min(chatInputContainerElement.scrollHeight, 200) + 'px';
|
||||
chatInputContainerElement.scrollTop = chatInputContainerElement.scrollHeight;
|
||||
}
|
||||
|
||||
await tick();
|
||||
@@ -93,12 +91,12 @@
|
||||
<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."
|
||||
content={$i18n.t('This chat won’t appear in history and your messages will not be saved.')}
|
||||
className="w-full flex justify-center mb-0.5"
|
||||
placement="top"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-gray-500 font-medium text-lg my-2 w-fit">
|
||||
<EyeSlash strokeWidth="2.5" className="size-5" /> Temporary Chat
|
||||
<EyeSlash strokeWidth="2.5" className="size-5" />{$i18n.t('Temporary Chat')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
@@ -142,7 +140,7 @@
|
||||
{#if models[selectedModelIdx]?.name}
|
||||
{models[selectedModelIdx]?.name}
|
||||
{:else}
|
||||
{$i18n.t('Hello, {{name}}', { name: $user.name })}
|
||||
{$i18n.t('Hello, {{name}}', { name: $user?.name })}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
let profileImageInputElement: HTMLInputElement;
|
||||
|
||||
const submitHandler = async () => {
|
||||
if (name !== $user.name) {
|
||||
if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') {
|
||||
if (name !== $user?.name) {
|
||||
if (profileImageUrl === generateInitialsImage($user?.name) || profileImageUrl === '') {
|
||||
profileImageUrl = generateInitialsImage(name);
|
||||
}
|
||||
}
|
||||
@@ -75,8 +75,8 @@
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
name = $user.name;
|
||||
profileImageUrl = $user.profile_image_url;
|
||||
name = $user?.name;
|
||||
profileImageUrl = $user?.profile_image_url;
|
||||
webhookUrl = $settings?.notifications?.webhook_url ?? '';
|
||||
|
||||
APIKey = await getAPIKey(localStorage.token).catch((error) => {
|
||||
@@ -214,7 +214,7 @@
|
||||
<button
|
||||
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
|
||||
on:click={async () => {
|
||||
const url = await getGravatarUrl(localStorage.token, $user.email);
|
||||
const url = await getGravatarUrl(localStorage.token, $user?.email);
|
||||
|
||||
profileImageUrl = url;
|
||||
}}>{$i18n.t('Use Gravatar')}</button
|
||||
@@ -236,10 +236,11 @@
|
||||
|
||||
<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-hidden"
|
||||
class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
bind:value={name}
|
||||
required
|
||||
placeholder={$i18n.t('Enter your name')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -268,7 +269,7 @@
|
||||
<UpdatePassword />
|
||||
</div>
|
||||
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-4" />
|
||||
<hr class="border-gray-50 dark:border-gray-850 my-2" />
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<div class=" font-medium">{$i18n.t('API keys')}</div>
|
||||
|
||||
@@ -786,62 +786,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Repeat Penalty (Ollama)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.repeat_penalty = (params?.repeat_penalty ?? null) === null ? 1.1 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.repeat_penalty ?? 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?.repeat_penalty ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-2"
|
||||
max="2"
|
||||
step="0.05"
|
||||
bind:value={params.repeat_penalty}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
bind:value={params.repeat_penalty}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-2"
|
||||
max="2"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t('Sets how far back for the model to look back to prevent repetition.')}
|
||||
@@ -952,6 +896,172 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.num_keep ?? 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?.num_keep ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-1"
|
||||
max="10240000"
|
||||
step="1"
|
||||
bind:value={params.num_keep}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div class="">
|
||||
<input
|
||||
bind:value={params.num_keep}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-1"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Max Tokens (num_predict)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.max_tokens ?? 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?.max_tokens ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-2"
|
||||
max="131072"
|
||||
step="1"
|
||||
bind:value={params.max_tokens}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
bind:value={params.max_tokens}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-2"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Repeat Penalty (Ollama)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.repeat_penalty = (params?.repeat_penalty ?? null) === null ? 1.1 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.repeat_penalty ?? 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?.repeat_penalty ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-2"
|
||||
max="2"
|
||||
step="0.05"
|
||||
bind:value={params.repeat_penalty}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
bind:value={params.repeat_penalty}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-2"
|
||||
max="2"
|
||||
step="any"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t('Sets the size of the context window used to generate the next token.')}
|
||||
@@ -1061,116 +1171,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.num_keep = (params?.num_keep ?? null) === null ? 24 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.num_keep ?? 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?.num_keep ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-1"
|
||||
max="10240000"
|
||||
step="1"
|
||||
bind:value={params.num_keep}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div class="">
|
||||
<input
|
||||
bind:value={params.num_keep}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-1"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
content={$i18n.t(
|
||||
'This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.'
|
||||
)}
|
||||
placement="top-start"
|
||||
className="inline-tooltip"
|
||||
>
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Max Tokens (num_predict)')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded-sm transition shrink-0 outline-hidden"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
params.max_tokens = (params?.max_tokens ?? null) === null ? 128 : null;
|
||||
}}
|
||||
>
|
||||
{#if (params?.max_tokens ?? 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?.max_tokens ?? null) !== null}
|
||||
<div class="flex mt-0.5 space-x-2">
|
||||
<div class=" flex-1">
|
||||
<input
|
||||
id="steps-range"
|
||||
type="range"
|
||||
min="-2"
|
||||
max="131072"
|
||||
step="1"
|
||||
bind:value={params.max_tokens}
|
||||
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
bind:value={params.max_tokens}
|
||||
type="number"
|
||||
class=" bg-transparent text-center w-14"
|
||||
min="-2"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if admin}
|
||||
<div class=" py-0.5 w-full justify-between">
|
||||
<Tooltip
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={voice}
|
||||
placeholder="Select a voice"
|
||||
/>
|
||||
@@ -330,7 +330,7 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={voice}
|
||||
>
|
||||
<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
|
||||
@@ -361,7 +361,7 @@
|
||||
<div class="flex-1">
|
||||
<input
|
||||
list="voice-list"
|
||||
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
bind:value={voice}
|
||||
placeholder="Select a voice"
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import ArchivedChatsModal from '$lib/components/layout/Sidebar/ArchivedChatsModal.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
let showArchiveConfirm = false;
|
||||
let showDeleteConfirm = false;
|
||||
let showArchivedChatsModal = false;
|
||||
|
||||
let chatImportInputElement: HTMLInputElement;
|
||||
|
||||
@@ -95,8 +97,16 @@
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
|
||||
const handleArchivedChatsChange = async () => {
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
scrollPaginationEnabled.set(true);
|
||||
};
|
||||
</script>
|
||||
|
||||
<ArchivedChatsModal bind:show={showArchivedChatsModal} on:change={handleArchivedChatsChange} />
|
||||
|
||||
<div class="flex flex-col h-full justify-between space-y-3 text-sm">
|
||||
<div class=" space-y-2 overflow-y-scroll max-h-[28rem] lg:max-h-full">
|
||||
<div class="flex flex-col">
|
||||
@@ -157,6 +167,32 @@
|
||||
<hr class=" border-gray-100 dark:border-gray-850" />
|
||||
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
on:click={() => {
|
||||
showArchivedChatsModal = true;
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
d="M3.375 3C2.339 3 1.5 3.84 1.5 4.875v.75c0 1.036.84 1.875 1.875 1.875h17.25c1.035 0 1.875-.84 1.875-1.875v-.75C22.5 3.839 21.66 3 20.625 3H3.375Z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="m3.087 9 .54 9.176A3 3 0 0 0 6.62 21h10.757a3 3 0 0 0 2.995-2.824L20.913 9H3.087ZM12 10.5a.75.75 0 0 1 .75.75v4.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 1 1 1.06-1.06l1.72 1.72v-4.94a.75.75 0 0 1 .75-.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Archived Chats')}</div>
|
||||
</button>
|
||||
|
||||
{#if showArchiveConfirm}
|
||||
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
|
||||
<div class="flex items-center space-x-3">
|
||||
|
||||
@@ -308,15 +308,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user.role === 'admin' || $user?.permissions.chat?.controls}
|
||||
<hr class="border-gray-100 dark:border-gray-850 my-3" />
|
||||
{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
|
||||
<hr class="border-gray-50 dark:border-gray-850 my-3" />
|
||||
|
||||
<div>
|
||||
<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
|
||||
<textarea
|
||||
<Textarea
|
||||
bind:value={system}
|
||||
class="w-full rounded-lg p-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-hidden resize-none"
|
||||
className="w-full text-sm bg-white dark:text-gray-300 dark:bg-gray-900 outline-hidden resize-none"
|
||||
rows="4"
|
||||
placeholder={$i18n.t('Enter system prompt here')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -358,7 +359,7 @@
|
||||
{#if keepAlive !== null}
|
||||
<div class="flex mt-1 space-x-2">
|
||||
<input
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
type="text"
|
||||
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
|
||||
bind:value={keepAlive}
|
||||
@@ -398,7 +399,7 @@
|
||||
{#if requestFormat !== null}
|
||||
<div class="flex mt-1 space-x-2">
|
||||
<Textarea
|
||||
className="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
||||
className="w-full text-sm dark:text-gray-300 dark:bg-gray-900 outline-hidden"
|
||||
placeholder={$i18n.t('e.g. "json" or a JSON schema')}
|
||||
bind:value={requestFormat}
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
let landingPageMode = '';
|
||||
let chatBubble = true;
|
||||
let chatDirection: 'LTR' | 'RTL' = 'LTR';
|
||||
let chatDirection: 'LTR' | 'RTL' | 'auto' = 'auto';
|
||||
let ctrlEnterToSend = false;
|
||||
|
||||
let collapseCodeBlocks = false;
|
||||
@@ -211,7 +211,13 @@
|
||||
};
|
||||
|
||||
const toggleChangeChatDirection = async () => {
|
||||
chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
|
||||
if (chatDirection === 'auto') {
|
||||
chatDirection = 'LTR';
|
||||
} else if (chatDirection === 'LTR') {
|
||||
chatDirection = 'RTL';
|
||||
} else if (chatDirection === 'RTL') {
|
||||
chatDirection = 'auto';
|
||||
}
|
||||
saveSettings({ chatDirection });
|
||||
};
|
||||
|
||||
@@ -257,7 +263,7 @@
|
||||
widescreenMode = $settings.widescreenMode ?? false;
|
||||
splitLargeChunks = $settings.splitLargeChunks ?? false;
|
||||
scrollOnBranchChange = $settings.scrollOnBranchChange ?? true;
|
||||
chatDirection = $settings.chatDirection ?? 'LTR';
|
||||
chatDirection = $settings.chatDirection ?? 'auto';
|
||||
userLocation = $settings.userLocation ?? false;
|
||||
|
||||
notificationSound = $settings.notificationSound ?? true;
|
||||
@@ -412,8 +418,10 @@
|
||||
>
|
||||
{#if chatDirection === 'LTR'}
|
||||
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
|
||||
{:else}
|
||||
{:else if chatDirection === 'RTL'}
|
||||
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Auto')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
@@ -441,7 +449,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $user.role === 'admin'}
|
||||
{#if $user?.role === 'admin'}
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
|
||||
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
|
||||
@@ -94,7 +94,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each memories as memory}
|
||||
<tr class="border-b dark:border-gray-850 items-center">
|
||||
<tr class="border-b border-gray-50 dark:border-gray-850 items-center">
|
||||
<td class="px-3 py-1">
|
||||
<div class="line-clamp-1">
|
||||
{memory.content}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
|
||||
<AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} direct />
|
||||
|
||||
<form
|
||||
class="flex flex-col h-full justify-between text-sm"
|
||||
@@ -74,9 +74,8 @@
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each servers as server, idx}
|
||||
<Connection
|
||||
bind:url={server.url}
|
||||
bind:key={server.key}
|
||||
bind:config={server.config}
|
||||
bind:connection={server}
|
||||
direct
|
||||
onSubmit={() => {
|
||||
updateHandler();
|
||||
}}
|
||||
@@ -98,6 +97,14 @@
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" text-xs text-gray-600 dark:text-gray-300 mb-2">
|
||||
<a
|
||||
class="underline"
|
||||
href="https://github.com/open-webui/openapi-servers"
|
||||
target="_blank">{$i18n.t('Learn more about OpenAPI tool servers.')}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -11,11 +11,8 @@
|
||||
export let onDelete = () => {};
|
||||
export let onSubmit = () => {};
|
||||
|
||||
export let pipeline = false;
|
||||
|
||||
export let url = '';
|
||||
export let key = '';
|
||||
export let config = {};
|
||||
export let connection = null;
|
||||
export let direct = false;
|
||||
|
||||
let showConfigModal = false;
|
||||
let showDeleteConfirmDialog = false;
|
||||
@@ -23,21 +20,15 @@
|
||||
|
||||
<AddServerModal
|
||||
edit
|
||||
direct
|
||||
{direct}
|
||||
bind:show={showConfigModal}
|
||||
connection={{
|
||||
url,
|
||||
key,
|
||||
config
|
||||
}}
|
||||
{connection}
|
||||
onDelete={() => {
|
||||
showDeleteConfirmDialog = true;
|
||||
}}
|
||||
onSubmit={(connection) => {
|
||||
url = connection.url;
|
||||
key = connection.key;
|
||||
config = connection.config;
|
||||
onSubmit(connection);
|
||||
onSubmit={(c) => {
|
||||
connection = c;
|
||||
onSubmit(c);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -52,12 +43,12 @@
|
||||
<div class="flex w-full gap-2 items-center">
|
||||
<Tooltip
|
||||
className="w-full relative"
|
||||
content={$i18n.t(`WebUI will make requests to "{{url}}/openapi.json"`, {
|
||||
url: url
|
||||
content={$i18n.t(`WebUI will make requests to "{{url}}"`, {
|
||||
url: `${connection?.url}/${connection?.path ?? 'openapi.json'}`
|
||||
})}
|
||||
placement="top-start"
|
||||
>
|
||||
{#if !(config?.enable ?? true)}
|
||||
{#if !(connection?.config?.enable ?? true)}
|
||||
<div
|
||||
class="absolute top-0 bottom-0 left-0 right-0 opacity-60 bg-white dark:bg-gray-900 z-10"
|
||||
></div>
|
||||
@@ -65,19 +56,21 @@
|
||||
<div class="flex w-full">
|
||||
<div class="flex-1 relative">
|
||||
<input
|
||||
class=" outline-hidden w-full bg-transparent {pipeline ? 'pr-8' : ''}"
|
||||
class=" outline-hidden w-full bg-transparent"
|
||||
placeholder={$i18n.t('API Base URL')}
|
||||
bind:value={url}
|
||||
bind:value={connection.url}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SensitiveInput
|
||||
inputClassName=" outline-hidden bg-transparent w-full"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={key}
|
||||
required={false}
|
||||
/>
|
||||
{#if (connection?.auth_type ?? 'bearer') === 'bearer'}
|
||||
<SensitiveInput
|
||||
inputClassName=" outline-hidden bg-transparent w-full"
|
||||
placeholder={$i18n.t('API Key')}
|
||||
bind:value={connection.key}
|
||||
required={false}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
@@ -462,7 +462,7 @@
|
||||
<div class=" self-center">{$i18n.t('Interface')}</div>
|
||||
</button>
|
||||
{:else if tabId === 'connections'}
|
||||
{#if $user.role === 'admin' || ($user.role === 'user' && $config?.features?.enable_direct_connections)}
|
||||
{#if $user?.role === 'admin' || ($user?.role === 'user' && $config?.features?.enable_direct_connections)}
|
||||
<button
|
||||
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
|
||||
'connections'
|
||||
@@ -488,7 +488,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
{:else if tabId === 'tools'}
|
||||
{#if $user.role === 'admin' || ($user.role === 'user' && $config?.features?.enable_direct_tools)}
|
||||
{#if $user?.role === 'admin' || ($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers)}
|
||||
<button
|
||||
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
|
||||
'tools'
|
||||
@@ -636,7 +636,7 @@
|
||||
<div class=" self-center">{$i18n.t('About')}</div>
|
||||
</button>
|
||||
{:else if tabId === 'admin'}
|
||||
{#if $user.role === 'admin'}
|
||||
{#if $user?.role === 'admin'}
|
||||
<button
|
||||
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
|
||||
'admin'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { models, config, toolServers } from '$lib/stores';
|
||||
import { models, config, toolServers, tools } from '$lib/stores';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
|
||||
@@ -11,6 +11,11 @@
|
||||
import Collapsible from '../common/Collapsible.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let selectedToolIds = [];
|
||||
|
||||
let selectedTools = [];
|
||||
|
||||
$: selectedTools = $tools.filter((tool) => selectedToolIds.includes(tool.id));
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
</script>
|
||||
@@ -18,7 +23,7 @@
|
||||
<Modal bind:show size="md">
|
||||
<div>
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-0.5">
|
||||
<div class=" text-lg font-medium self-center">{$i18n.t('Available Tool Servers')}</div>
|
||||
<div class=" text-lg font-medium self-center">{$i18n.t('Available Tools')}</div>
|
||||
<button
|
||||
class="self-center"
|
||||
on:click={() => {
|
||||
@@ -38,47 +43,85 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 w-full flex flex-col justify-center">
|
||||
<div class=" text-sm dark:text-gray-300 mb-2">
|
||||
Open WebUI can use tools provided by any OpenAPI server. <br /><a
|
||||
class="underline"
|
||||
href="https://github.com/open-webui/openapi-servers"
|
||||
target="_blank">Learn more about OpenAPI tool servers.</a
|
||||
>
|
||||
</div>
|
||||
<div class=" text-sm dark:text-gray-300 mb-1">
|
||||
{#each $toolServers as toolServer}
|
||||
<Collapsible buttonClassName="w-full" chevron>
|
||||
<div>
|
||||
<div class="text-base font-medium dark:text-gray-100 text-gray-800">
|
||||
{toolServer?.openapi?.info?.title} - v{toolServer?.openapi?.info?.version}
|
||||
</div>
|
||||
{#if selectedTools.length > 0}
|
||||
{#if $toolServers.length > 0}
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 pb-1">
|
||||
<div class=" text-base font-medium self-center">{$i18n.t('Tools')}</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="text-sm text-gray-500">
|
||||
{toolServer?.openapi?.info?.description}
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500">
|
||||
{toolServer?.url}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="content">
|
||||
{#each toolServer?.specs ?? [] as tool_spec}
|
||||
<div class="my-1">
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100">
|
||||
{tool_spec?.name}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{tool_spec?.description}
|
||||
</div>
|
||||
<div class="px-5 pb-3 w-full flex flex-col justify-center">
|
||||
<div class=" text-sm dark:text-gray-300 mb-1">
|
||||
{#each selectedTools as tool}
|
||||
<Collapsible buttonClassName="w-full mb-0.5">
|
||||
<div>
|
||||
<div class="text-sm font-medium dark:text-gray-100 text-gray-800">
|
||||
{tool?.name}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible>
|
||||
{/each}
|
||||
|
||||
{#if tool?.meta?.description}
|
||||
<div class="text-xs text-gray-500">
|
||||
{tool?.meta?.description}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- <div slot="content">
|
||||
{JSON.stringify(tool, null, 2)}
|
||||
</div> -->
|
||||
</Collapsible>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $toolServers.length > 0}
|
||||
<div class=" flex justify-between dark:text-gray-300 px-5 pb-0.5">
|
||||
<div class=" text-base font-medium self-center">{$i18n.t('Tool Servers')}</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 w-full flex flex-col justify-center">
|
||||
<div class=" text-xs text-gray-600 dark:text-gray-300 mb-2">
|
||||
{$i18n.t('Open WebUI can use tools provided by any OpenAPI server.')} <br /><a
|
||||
class="underline"
|
||||
href="https://github.com/open-webui/openapi-servers"
|
||||
target="_blank">{$i18n.t('Learn more about OpenAPI tool servers.')}</a
|
||||
>
|
||||
</div>
|
||||
<div class=" text-sm dark:text-gray-300 mb-1">
|
||||
{#each $toolServers as toolServer}
|
||||
<Collapsible buttonClassName="w-full" chevron>
|
||||
<div>
|
||||
<div class="text-sm font-medium dark:text-gray-100 text-gray-800">
|
||||
{toolServer?.openapi?.info?.title} - v{toolServer?.openapi?.info?.version}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
{toolServer?.openapi?.info?.description}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
{toolServer?.url}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="content">
|
||||
{#each toolServer?.specs ?? [] as tool_spec}
|
||||
<div class="my-1">
|
||||
<div class="font-medium text-gray-800 dark:text-gray-100">
|
||||
{tool_spec?.name}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{tool_spec?.description}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Collapsible>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { decode } from 'html-entities';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getContext, createEventDispatcher } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
@@ -36,6 +37,7 @@
|
||||
import Spinner from './Spinner.svelte';
|
||||
import CodeBlock from '../chat/Messages/CodeBlock.svelte';
|
||||
import Markdown from '../chat/Messages/Markdown.svelte';
|
||||
import Image from './Image.svelte';
|
||||
|
||||
export let open = false;
|
||||
|
||||
@@ -53,9 +55,19 @@
|
||||
export let disabled = false;
|
||||
export let hide = false;
|
||||
|
||||
function formatJSONString(obj) {
|
||||
const collapsibleId = uuidv4();
|
||||
|
||||
function parseJSONString(str) {
|
||||
try {
|
||||
const parsed = JSON.parse(JSON.parse(obj));
|
||||
return parseJSONString(JSON.parse(str));
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
function formatJSONString(str) {
|
||||
try {
|
||||
const parsed = parseJSONString(str);
|
||||
// If parsed is an object/array, then it's valid JSON
|
||||
if (typeof parsed === 'object') {
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
@@ -65,7 +77,7 @@
|
||||
}
|
||||
} catch (e) {
|
||||
// Not valid JSON, return as-is
|
||||
return obj;
|
||||
return str;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -119,15 +131,15 @@
|
||||
{:else if attributes?.type === 'tool_calls'}
|
||||
{#if attributes?.done === 'true'}
|
||||
<Markdown
|
||||
id={`tool-calls-${attributes?.id}`}
|
||||
content={$i18n.t('View Result from `{{NAME}}`', {
|
||||
id={`${collapsibleId}-tool-calls-${attributes?.id}`}
|
||||
content={$i18n.t('View Result from **{{NAME}}**', {
|
||||
NAME: attributes.name
|
||||
})}
|
||||
/>
|
||||
{:else}
|
||||
<Markdown
|
||||
id={`tool-calls-${attributes?.id}`}
|
||||
content={$i18n.t('Executing `{{NAME}}`...', {
|
||||
id={`${collapsibleId}-tool-calls-${attributes?.id}-executing`}
|
||||
content={$i18n.t('Executing **{{NAME}}**...', {
|
||||
NAME: attributes.name
|
||||
})}
|
||||
/>
|
||||
@@ -188,32 +200,55 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !grow}
|
||||
{#if open && !hide}
|
||||
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
|
||||
{#if attributes?.type === 'tool_calls'}
|
||||
{@const args = decode(attributes?.arguments)}
|
||||
{@const result = decode(attributes?.result ?? '')}
|
||||
{#if attributes?.type === 'tool_calls'}
|
||||
{@const args = decode(attributes?.arguments)}
|
||||
{@const result = decode(attributes?.result ?? '')}
|
||||
{@const files = parseJSONString(decode(attributes?.files ?? ''))}
|
||||
|
||||
{#if attributes?.done === 'true'}
|
||||
<Markdown
|
||||
id={`tool-calls-${attributes?.id}-result`}
|
||||
content={`> \`\`\`json
|
||||
{#if !grow}
|
||||
{#if open && !hide}
|
||||
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
|
||||
{#if attributes?.type === 'tool_calls'}
|
||||
{#if attributes?.done === 'true'}
|
||||
<Markdown
|
||||
id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
|
||||
content={`> \`\`\`json
|
||||
> ${formatJSONString(args)}
|
||||
> ${formatJSONString(result)}
|
||||
> \`\`\``}
|
||||
/>
|
||||
{:else}
|
||||
<Markdown
|
||||
id={`tool-calls-${attributes?.id}-result`}
|
||||
content={`> \`\`\`json
|
||||
/>
|
||||
{:else}
|
||||
<Markdown
|
||||
id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
|
||||
content={`> \`\`\`json
|
||||
> ${formatJSONString(args)}
|
||||
> \`\`\``}
|
||||
/>
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<slot name="content" />
|
||||
{/if}
|
||||
{:else}
|
||||
<slot name="content" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if attributes?.done === 'true'}
|
||||
{#if typeof files === 'object'}
|
||||
{#each files ?? [] as file, idx}
|
||||
{#if file.startsWith('data:image/')}
|
||||
<Image
|
||||
id={`${collapsibleId}-tool-calls-${attributes?.id}-result-${idx}`}
|
||||
src={file}
|
||||
alt="Image"
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if !grow}
|
||||
{#if open && !hide}
|
||||
<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
export let show = true;
|
||||
export let size = 'md';
|
||||
export let containerClassName = 'p-3';
|
||||
export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
|
||||
export let className = 'bg-white dark:bg-gray-900 rounded-2xl';
|
||||
|
||||
let modalElement = null;
|
||||
let mounted = false;
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
{#if $user !== undefined}
|
||||
<UserMenu
|
||||
className="max-w-[200px]"
|
||||
role={$user.role}
|
||||
role={$user?.role}
|
||||
on:show={(e) => {
|
||||
if (e.detail === 'archived-chat') {
|
||||
showArchivedChats.set(true);
|
||||
@@ -180,7 +180,7 @@
|
||||
>
|
||||
<div class=" self-center">
|
||||
<img
|
||||
src={$user.profile_image_url}
|
||||
src={$user?.profile_image_url}
|
||||
class="size-6 object-cover rounded-full"
|
||||
alt="User profile"
|
||||
draggable="false"
|
||||
|
||||
@@ -446,7 +446,7 @@
|
||||
});
|
||||
|
||||
if (res) {
|
||||
$socket.emit('join-channels', { auth: { token: $user.token } });
|
||||
$socket.emit('join-channels', { auth: { token: $user?.token } });
|
||||
await initChannels();
|
||||
showCreateChannel = false;
|
||||
}
|
||||
@@ -627,13 +627,13 @@
|
||||
? 'opacity-20'
|
||||
: ''}"
|
||||
>
|
||||
{#if $config?.features?.enable_channels && ($user.role === 'admin' || $channels.length > 0) && !search}
|
||||
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || $channels.length > 0) && !search}
|
||||
<Folder
|
||||
className="px-2 mt-0.5"
|
||||
name={$i18n.t('Channels')}
|
||||
dragAndDrop={false}
|
||||
onAdd={async () => {
|
||||
if ($user.role === 'admin') {
|
||||
if ($user?.role === 'admin') {
|
||||
await tick();
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -891,9 +891,9 @@
|
||||
|
||||
<div class="px-2">
|
||||
<div class="flex flex-col font-primary">
|
||||
{#if $user !== undefined}
|
||||
{#if $user !== undefined && $user !== null}
|
||||
<UserMenu
|
||||
role={$user.role}
|
||||
role={$user?.role}
|
||||
on:show={(e) => {
|
||||
if (e.detail === 'archived-chat') {
|
||||
showArchivedChats.set(true);
|
||||
@@ -908,12 +908,12 @@
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<img
|
||||
src={$user.profile_image_url}
|
||||
src={$user?.profile_image_url}
|
||||
class=" max-w-[30px] object-cover rounded-full"
|
||||
alt="User profile"
|
||||
/>
|
||||
</div>
|
||||
<div class=" self-center font-medium">{$user.name}</div>
|
||||
<div class=" self-center font-medium">{$user?.name}</div>
|
||||
</button>
|
||||
</UserMenu>
|
||||
{/if}
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
|
||||
<thead
|
||||
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
|
||||
class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 border-gray-50 dark:border-gray-850"
|
||||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
|
||||
@@ -150,7 +150,7 @@
|
||||
.includes(searchValue.toLowerCase())) as chat, idx}
|
||||
<tr
|
||||
class="bg-transparent {idx !== chats.length - 1 &&
|
||||
'border-b'} dark:bg-gray-900 dark:border-gray-850 text-xs"
|
||||
'border-b'} dark:bg-gray-900 border-gray-50 dark:border-gray-850 text-xs"
|
||||
>
|
||||
<td class="px-3 py-1 w-2/3">
|
||||
<a href="/c/{chat.id}" target="_blank">
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
{#if focused && (filteredOptions.length > 0 || filteredTags.length > 0)}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="absolute top-0 mt-8 left-0 right-1 border dark:border-gray-900 bg-gray-50 dark:bg-gray-950 rounded-lg z-10 shadow-lg"
|
||||
class="absolute top-0 mt-8 left-0 right-1 border border-gray-100 dark:border-gray-900 bg-gray-50 dark:bg-gray-950 rounded-lg z-10 shadow-lg"
|
||||
in:fade={{ duration: 50 }}
|
||||
on:mouseenter={() => {
|
||||
selectedIdx = null;
|
||||
|
||||
@@ -93,13 +93,10 @@
|
||||
|
||||
const tab = await window.open(`${url}/models/create`, '_blank');
|
||||
|
||||
// Define the event handler function
|
||||
const messageHandler = (event) => {
|
||||
if (event.origin !== url) return;
|
||||
if (event.data === 'loaded') {
|
||||
tab.postMessage(JSON.stringify(model), '*');
|
||||
|
||||
// Remove the event listener after handling the message
|
||||
window.removeEventListener('message', messageHandler);
|
||||
}
|
||||
};
|
||||
@@ -477,29 +474,33 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
downloadModels(models);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Models')}</div>
|
||||
{#if models.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
downloadModels(models);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
||||
{$i18n.t('Export Models')}
|
||||
</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3.5 h-3.5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-3.5 h-3.5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -285,33 +285,36 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
// promptsImportInputElement.click();
|
||||
let blob = new Blob([JSON.stringify(prompts)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `prompts-export-${Date.now()}.json`);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Prompts')}</div>
|
||||
{#if prompts.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
let blob = new Blob([JSON.stringify(prompts)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `prompts-export-${Date.now()}.json`);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">
|
||||
{$i18n.t('Export Prompts')}
|
||||
</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -71,13 +71,10 @@
|
||||
|
||||
const tab = await window.open(`${url}/tools/create`, '_blank');
|
||||
|
||||
// Define the event handler function
|
||||
const messageHandler = (event) => {
|
||||
if (event.origin !== url) return;
|
||||
if (event.data === 'loaded') {
|
||||
tab.postMessage(JSON.stringify(item), '*');
|
||||
|
||||
// Remove the event listener after handling the message
|
||||
window.removeEventListener('message', messageHandler);
|
||||
}
|
||||
};
|
||||
@@ -124,8 +121,7 @@
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Tool deleted successfully'));
|
||||
|
||||
init();
|
||||
await init();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -398,39 +394,41 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
const _tools = await exportTools(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (_tools) {
|
||||
let blob = new Blob([JSON.stringify(_tools)], {
|
||||
type: 'application/json'
|
||||
{#if tools.length}
|
||||
<button
|
||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
|
||||
on:click={async () => {
|
||||
const _tools = await exportTools(localStorage.token).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
return null;
|
||||
});
|
||||
saveAs(blob, `tools-export-${Date.now()}.json`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Tools')}</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
if (_tools) {
|
||||
let blob = new Blob([JSON.stringify(_tools)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
saveAs(blob, `tools-export-${Date.now()}.json`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-2 font-medium line-clamp-1">{$i18n.t('Export Tools')}</div>
|
||||
|
||||
<div class=" self-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(الأخير)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ نماذج }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "دردشات {{user}}",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "صوتي",
|
||||
"August": "أغسطس",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "النسخ التلقائي للاستجابة إلى الحافظة",
|
||||
"Auto-playback response": "استجابة التشغيل التلقائي",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 الرابط الرئيسي",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 الرابط مطلوب",
|
||||
"Available list": "",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "متاح",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "اتصالات",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "الاتصال",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "",
|
||||
"Discover a function": "",
|
||||
"Discover a model": "اكتشف نموذجا",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "أدخل كود اللغة",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "(e.g. {{modelTag}}) أدخل الموديل تاق",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "أدخل تسلسل التوقف",
|
||||
"Enter system prompt": "",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "أدخل البريد الاكتروني",
|
||||
"Enter Your Full Name": "أدخل الاسم كامل",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "ادخل كلمة المرور",
|
||||
"Enter Your Role": "أدخل الصلاحيات",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "تجريبي",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
"Failed to upload file.": "",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "عقوبة التردد",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "",
|
||||
"Open in full screen": "",
|
||||
"Open new chat": "فتح محادثة جديده",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI API.مطلوب مفتاح ",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "URL/مفتاح OpenAI.مطلوب عنوان ",
|
||||
"openapi.json Path": "",
|
||||
"or": "أو",
|
||||
"Organize your users": "",
|
||||
"Other": "آخر",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "",
|
||||
"This action cannot be undone. Do you wish to continue?": "",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "",
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "إصدار",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "Webhook الرابط",
|
||||
"WebUI Settings": "WebUI اعدادات",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "ما هو الجديد",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(последна)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "{{COUNT}} Отговори",
|
||||
"{{user}}'s Chats": "{{user}}'s чатове",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "Атрибут за потребителско име",
|
||||
"Audio": "Аудио",
|
||||
"August": "Август",
|
||||
"Auth": "",
|
||||
"Authenticate": "Удостоверяване",
|
||||
"Authentication": "Автентикация",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Автоматично копиране на отговор в клипборда",
|
||||
"Auto-playback response": "Автоматично възпроизвеждане на отговора",
|
||||
"Autocomplete Generation": "Генериране на автоматично довършване",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Базов URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Базов URL е задължителен.",
|
||||
"Available list": "Наличен списък",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "наличен!",
|
||||
"Awful": "Ужасно",
|
||||
"Azure AI Speech": "Azure AI Реч",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "Потвърдете новата си парола",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "Свържете се със собствени крайни точки на API, съвместими с OpenAI.",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Връзки",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "Свържете се с администратор за достъп до WebUI",
|
||||
"Content": "Съдържание",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "Директни връзки",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Директните връзки позволяват на потребителите да се свързват със собствени OpenAI съвместими API крайни точки.",
|
||||
"Direct Connections settings updated": "Настройките за директни връзки са актуализирани",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Деактивирано",
|
||||
"Discover a function": "Открийте функция",
|
||||
"Discover a model": "Открийте модел",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "Въведете API ключ за Kagi Search",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "Въведете кодове на езика",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Въведете ID на модела",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Въведете таг на модел (напр. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Въведете API ключ за Mojeek Search",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "Въведете порт на сървъра",
|
||||
"Enter stop sequence": "Въведете стоп последователност",
|
||||
"Enter system prompt": "Въведете системен промпт",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Въведете API ключ за Tavily",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Въведете публичния URL адрес на вашия WebUI. Този URL адрес ще бъде използван за генериране на връзки в известията.",
|
||||
"Enter Tika Server URL": "Въведете URL адрес на Tika сървър",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Въведете имейл",
|
||||
"Enter Your Full Name": "Въведете вашето пълно име",
|
||||
"Enter your message": "Въведете съобщението си",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "Въведете новата си парола",
|
||||
"Enter Your Password": "Въведете вашата парола",
|
||||
"Enter Your Role": "Въведете вашата роля",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "Изключи",
|
||||
"Execute code for analysis": "Изпълнете код за анализ",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Експериментално",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "Неуспешно създаване на API ключ.",
|
||||
"Failed to fetch models": "Неуспешно извличане на модели",
|
||||
"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "Неуспешно запазване на конфигурацията на моделите",
|
||||
"Failed to update settings": "Неуспешно актуализиране на настройките",
|
||||
"Failed to upload file.": "Неуспешно качване на файл.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "Изковете нови пътища",
|
||||
"Form": "Форма",
|
||||
"Format your variables using brackets like this:": "Форматирайте вашите променливи, използвайки скоби като това:",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Наказание за честота",
|
||||
"Full Context Mode": "Режим на пълен контекст",
|
||||
"Function": "Функция",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "LDAP",
|
||||
"LDAP server updated": "LDAP сървърът е актуализиран",
|
||||
"Leaderboard": "Класация",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Оставете празно за неограничено",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Model": "Модел",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Отвори файл",
|
||||
"Open in full screen": "Отвори на цял екран",
|
||||
"Open new chat": "Отвори нов чат",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "Open WebUI използва вътрешно faster-whisper.",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI използва SpeechT5 и CMU Arctic говорителни вграждания.",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Версията на Open WebUI (v{{OPEN_WEBUI_VERSION}}) е по-ниска от необходимата версия (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI API ключ е задължителен.",
|
||||
"OpenAI API settings updated": "Настройките на OpenAI API са актуализирани",
|
||||
"OpenAI URL/Key required.": "OpenAI URL/Key е задължителен.",
|
||||
"openapi.json Path": "",
|
||||
"or": "или",
|
||||
"Organize your users": "Организирайте вашите потребители",
|
||||
"Other": "Друго",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Моля, внимателно прегледайте следните предупреждения:",
|
||||
"Please do not close the settings page while loading the model.": "Моля, не затваряйте страницата с настройки, докато моделът се зарежда.",
|
||||
"Please enter a prompt": "Моля, въведете промпт",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "Моля, попълнете всички полета.",
|
||||
"Please select a model first.": "Моля, първо изберете модел.",
|
||||
"Please select a model.": "Моля, изберете модел.",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Мисля...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Това действие не може да бъде отменено. Желаете ли да продължите?",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Това гарантира, че ценните ви разговори се запазват сигурно във вашата бекенд база данни. Благодарим ви!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Това е експериментална функция, може да не работи според очакванията и подлежи на промяна по всяко време.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "ID на инструмента",
|
||||
"Tool imported successfully": "Инструментът е импортиран успешно",
|
||||
"Tool Name": "Име на инструмента",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Инструментът е актуализиран успешно",
|
||||
"Tools": "Инструменти",
|
||||
"Tools Access": "Достъп до инструменти",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Версия",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Версия {{selectedVersion}} от {{totalVersions}}",
|
||||
"View Replies": "Преглед на отговорите",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "Видимост",
|
||||
"Voice": "Глас",
|
||||
"Voice Input": "Гласов вход",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "Уебхук URL",
|
||||
"WebUI Settings": "WebUI Настройки",
|
||||
"WebUI URL": "WebUI URL",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ще прави заявки към \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ще прави заявки към \"{{url}}/chat/completions\"",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "Какво се опитвате да постигнете?",
|
||||
"What are you working on?": "Върху какво работите?",
|
||||
"What’s New in": "",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(সর্বশেষ)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ মডেল}}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "{{user}}র চ্যাটস",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "অডিও",
|
||||
"August": "আগস্ট",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "রেসপন্সগুলো স্বয়ংক্রিভাবে ক্লিপবোর্ডে কপি হবে",
|
||||
"Auto-playback response": "রেসপন্স অটো-প্লেব্যাক",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 বেজ ইউআরএল",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 বেজ ইউআরএল আবশ্যক",
|
||||
"Available list": "",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "উপলব্ধ!",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "কানেকশনগুলো",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "বিষয়বস্তু",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "",
|
||||
"Discover a function": "",
|
||||
"Discover a model": "একটি মডেল আবিষ্কার করুন",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "ল্যাঙ্গুয়েজ কোড লিখুন",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "মডেল ট্যাগ লিখুন (e.g. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "স্টপ সিকোয়েন্স লিখুন",
|
||||
"Enter system prompt": "",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "আপনার ইমেইল লিখুন",
|
||||
"Enter Your Full Name": "আপনার পূর্ণ নাম লিখুন",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "আপনার পাসওয়ার্ড লিখুন",
|
||||
"Enter Your Role": "আপনার রোল লিখুন",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "পরিক্ষামূলক",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
"Failed to upload file.": "",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "ফ্রিকোয়েন্সি পেনাল্টি",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "",
|
||||
"Open in full screen": "",
|
||||
"Open new chat": "নতুন চ্যাট খুলুন",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI API কোড আবশ্যক",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "OpenAI URL/Key আবশ্যক",
|
||||
"openapi.json Path": "",
|
||||
"or": "অথবা",
|
||||
"Organize your users": "",
|
||||
"Other": "অন্যান্য",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "",
|
||||
"This action cannot be undone. Do you wish to continue?": "",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "এটা নিশ্চিত করে যে, আপনার গুরুত্বপূর্ণ আলোচনা নিরাপদে আপনার ব্যাকএন্ড ডেটাবেজে সংরক্ষিত আছে। ধন্যবাদ!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "",
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "ভার্সন",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "ওয়েবহুক URL",
|
||||
"WebUI Settings": "WebUI সেটিংসমূহ",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "এতে নতুন কী",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(últim)",
|
||||
"(Ollama)": "(Ollama)",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tool Servers": "{{COUNT}} Servidors d'eines disponibles",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "{{COUNT}} línies ocultes",
|
||||
"{{COUNT}} Replies": "{{COUNT}} respostes",
|
||||
"{{user}}'s Chats": "Els xats de {{user}}",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "Atribut per al Nom d'usuari",
|
||||
"Audio": "Àudio",
|
||||
"August": "Agost",
|
||||
"Auth": "",
|
||||
"Authenticate": "Autenticar",
|
||||
"Authentication": "Autenticació",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Copiar la resposta automàticament al porta-retalls",
|
||||
"Auto-playback response": "Reproduir la resposta automàticament",
|
||||
"Autocomplete Generation": "Generació automàtica",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "URL Base d'AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Es requereix l'URL Base d'AUTOMATIC1111.",
|
||||
"Available list": "Llista de disponibles",
|
||||
"Available Tool Servers": "Servidors d'eines disponibles",
|
||||
"Available Tools": "",
|
||||
"available!": "disponible!",
|
||||
"Awful": "Terrible",
|
||||
"Azure AI Speech": "Azure AI Speech",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "Confirma la teva nova contrasenya",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "Connecta als teus propis punts de connexió de l'API compatible amb OpenAI",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "Connecta als teus propis servidors d'eines externs compatibles amb OpenAPI",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Connexions",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "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.",
|
||||
"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
|
||||
"Content": "Contingut",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "Connexions directes",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permeten als usuaris connectar-se als seus propis endpoints d'API compatibles amb OpenAI.",
|
||||
"Direct Connections settings updated": "Configuració de les connexions directes actualitzada",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Deshabilitat",
|
||||
"Discover a function": "Descobrir una funció",
|
||||
"Discover a model": "Descobrir un model",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "Introdueix la clau API de Kagi Search",
|
||||
"Enter Key Behavior": "Introdueix el comportament de clau",
|
||||
"Enter language codes": "Introdueix els codis de llenguatge",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Introdueix l'identificador del model",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "Introdueix el port del servidor",
|
||||
"Enter stop sequence": "Introdueix la seqüència de parada",
|
||||
"Enter system prompt": "Introdueix la indicació de sistema",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Introdueix la clau API de Tavily",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entra la URL pública de WebUI. Aquesta URL s'utilitzarà per generar els enllaços en les notificacions.",
|
||||
"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Introdueix el teu correu electrònic",
|
||||
"Enter Your Full Name": "Introdueix el teu nom complet",
|
||||
"Enter your message": "Introdueix el teu missatge",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "Introdueix la teva nova contrasenya",
|
||||
"Enter Your Password": "Introdueix la teva contrasenya",
|
||||
"Enter Your Role": "Introdueix el teu rol",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "S'ha superat el nombre de places a la vostra llicència. Poseu-vos en contacte amb el servei d'assistència per augmentar el nombre de places.",
|
||||
"Exclude": "Excloure",
|
||||
"Execute code for analysis": "Executar el codi per analitzar-lo",
|
||||
"Executing `{{NAME}}`...": "Executant `{{NAME}}`...",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "Expandir",
|
||||
"Experimental": "Experimental",
|
||||
"Explain": "Explicar",
|
||||
@@ -493,6 +502,7 @@
|
||||
"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 connections": "",
|
||||
"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.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "Crea nous camins",
|
||||
"Form": "Formulari",
|
||||
"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Penalització per freqüència",
|
||||
"Full Context Mode": "Mode de context complert",
|
||||
"Function": "Funció",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "LDAP",
|
||||
"LDAP server updated": "Servidor LDAP actualitzat",
|
||||
"Leaderboard": "Tauler de classificació",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Deixar-ho buit per il·limitat",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/api/tags\"",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/models\"",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Eta de Mirostat",
|
||||
"Mirostat Tau": "Tau de Mirostat",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Obrir arxiu",
|
||||
"Open in full screen": "Obrir en pantalla complerta",
|
||||
"Open new chat": "Obre un xat nou",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "Open WebUI utilitza faster-whisper internament.",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI utilitza incrustacions de SpeechT5 i CMU Arctic.",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "Es requereix la clau API d'OpenAI.",
|
||||
"OpenAI API settings updated": "Configuració de l'API d'OpenAI actualitzada",
|
||||
"OpenAI URL/Key required.": "URL/Clau d'OpenAI requerides.",
|
||||
"openapi.json Path": "",
|
||||
"or": "o",
|
||||
"Organize your users": "Organitza els teus usuaris",
|
||||
"Other": "Altres",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
|
||||
"Please do not close the settings page while loading the model.": "No tanquis la pàgina de configuració mentre carregues el model.",
|
||||
"Please enter a prompt": "Si us plau, entra una indicació",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"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.": "Si us plau, selecciona un model.",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Pensant...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Aquesta acció no es pot desfer. Vols continuar?",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Aquest canal es va crear el dia {{createdAt}}. Aquest és el començament del canal {{channelName}}.",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Aquesta opció controla quants tokens es conserven en actualitzar el context. Per exemple, si s'estableix en 2, es conservaran els darrers 2 tokens del context de conversa. Preservar el context pot ajudar a mantenir la continuïtat d'una conversa, però pot reduir la capacitat de respondre a nous temes.",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "ID de l'eina",
|
||||
"Tool imported successfully": "Eina importada correctament",
|
||||
"Tool Name": "Nom de l'eina",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Eina actualitzada correctament",
|
||||
"Tools": "Eines",
|
||||
"Tools Access": "Accés a les eines",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Versió",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
|
||||
"View Replies": "Veure les respostes",
|
||||
"View Result from `{{NAME}}`": "Veure el resultat de `{{NAME}}`",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "Visibilitat",
|
||||
"Voice": "Veu",
|
||||
"Voice Input": "Entrada de veu",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "URL del webhook",
|
||||
"WebUI Settings": "Preferències de WebUI",
|
||||
"WebUI URL": "URL de WebUI",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà peticions a \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà peticions a \"{{url}}/chat/completions\"",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "WebUI farà peticions a \"{{url}}/openapi.json\"",
|
||||
"What are you trying to achieve?": "Què intentes aconseguir?",
|
||||
"What are you working on?": "En què estàs treballant?",
|
||||
"What’s New in": "Què hi ha de nou a",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "Audio",
|
||||
"August": "",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Awtomatikong kopya sa tubag sa clipboard",
|
||||
"Auto-playback response": "Autoplay nga tubag",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "Base URL AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Ang AUTOMATIC1111 base URL gikinahanglan.",
|
||||
"Available list": "",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "magamit!",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Mga koneksyon",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "Kontento",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "",
|
||||
"Discover a function": "",
|
||||
"Discover a model": "",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Pagsulod sa template tag (e.g. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "Pagsulod sa katapusan nga han-ay",
|
||||
"Enter system prompt": "",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Pagsulod sa imong e-mail address",
|
||||
"Enter Your Full Name": "Ibutang ang imong tibuok nga ngalan",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Ibutang ang imong password",
|
||||
"Enter Your Role": "",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Eksperimento",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
"Failed to upload file.": "",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "",
|
||||
"Open in full screen": "",
|
||||
"Open new chat": "Ablihi ang bag-ong diskusyon",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "Ang yawe sa OpenAI API gikinahanglan.",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "",
|
||||
"openapi.json Path": "",
|
||||
"or": "O",
|
||||
"Organize your users": "",
|
||||
"Other": "",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "",
|
||||
"This action cannot be undone. Do you wish to continue?": "",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Kini nagsiguro nga ang imong bililhon nga mga panag-istoryahanay luwas nga natipig sa imong backend database. ",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "",
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Bersyon",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "",
|
||||
"WebUI Settings": "Mga Setting sa WebUI",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "Unsay bag-o sa",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "Nejnovější",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "{{user}}'s konverzace",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "Zvuk",
|
||||
"August": "Srpen",
|
||||
"Auth": "",
|
||||
"Authenticate": "Autentikace",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Automatické kopírování odpovědi do schránky",
|
||||
"Auto-playback response": "Automatická odpověď při přehrávání",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "Výchozí URL pro AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Vyžaduje se základní URL pro AUTOMATIC1111.",
|
||||
"Available list": "Dostupný seznam",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "k dispozici!",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "Azure AI syntéza řeči",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Připojení",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "Kontaktujte administrátora pro přístup k webovému rozhraní.",
|
||||
"Content": "Obsah",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Zakázáno",
|
||||
"Discover a function": "Objevit funkci",
|
||||
"Discover a model": "Objevte model",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "Zadejte kódy jazyků",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Zadejte ID modelu",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Zadejte označení modelu (např. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "Zadejte ukončovací sekvenci",
|
||||
"Enter system prompt": "Vložte systémový prompt",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Zadejte API klíč Tavily",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "Zadejte URL serveru Tika",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Zadejte svůj email",
|
||||
"Enter Your Full Name": "Zadejte své plné jméno",
|
||||
"Enter your message": "Zadejte svou zprávu",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Zadejte své heslo",
|
||||
"Enter Your Role": "Zadejte svou roli",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "Vyloučit",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Experimentální",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"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 connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "Nepodařilo se aktualizovat nastavení",
|
||||
"Failed to upload file.": "Nepodařilo se nahrát soubor.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "Formulář",
|
||||
"Format your variables using brackets like this:": "Formátujte své proměnné pomocí závorek takto:",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Penalizace frekvence",
|
||||
"Full Context Mode": "",
|
||||
"Function": "Funkce",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "Žebříček",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Nechte prázdné pro neomezeně",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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í.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Otevřít soubor",
|
||||
"Open in full screen": "Otevřít na celou obrazovku",
|
||||
"Open new chat": "Otevřít nový chat",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "Open WebUI interně používá faster-whisper.",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Verze Open WebUI (v{{OPEN_WEBUI_VERSION}}) je nižší než požadovaná verze (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "Je vyžadován klíč OpenAI API.",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "Je vyžadován odkaz/adresa URL nebo klíč OpenAI.",
|
||||
"openapi.json Path": "",
|
||||
"or": "nebo",
|
||||
"Organize your users": "",
|
||||
"Other": "Jiné",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Prosím, pečlivě si přečtěte následující upozornění:",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "Prosím, zadejte zadání.",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "Prosím, vyplňte všechna pole.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Přemýšlím...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Tuto akci nelze vrátit zpět. Přejete si pokračovat?",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zajišťuje, že vaše cenné konverzace jsou bezpečně uloženy ve vaší backendové databázi. Děkujeme!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Jedná se o experimentální funkci, nemusí fungovat podle očekávání a může být kdykoliv změněna.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "ID nástroje",
|
||||
"Tool imported successfully": "Nástroj byl úspěšně importován",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Nástroj byl úspěšně aktualizován.",
|
||||
"Tools": "Nástroje",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Verze",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Verze {{selectedVersion}} z {{totalVersions}}",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "Viditelnost",
|
||||
"Voice": "Hlas",
|
||||
"Voice Input": "Hlasový vstup",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "Webhook URL",
|
||||
"WebUI Settings": "Nastavení WebUI",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "Co je nového v",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(seneste)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ modeller }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "{{user}}s chats",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "Lyd",
|
||||
"August": "august",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Automatisk kopiering af svar til udklipsholder",
|
||||
"Auto-playback response": "Automatisk afspil svar",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL er påkrævet.",
|
||||
"Available list": "Tilgængelige lister",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "tilgængelig!",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "Azure AI Speech",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Forbindelser",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "Kontakt din administrator for adgang til WebUI",
|
||||
"Content": "Indhold",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Inaktiv",
|
||||
"Discover a function": "Find en funktion",
|
||||
"Discover a model": "Find en model",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "Indtast sprogkoder",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Indtast model-ID",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Indtast modelmærke (f.eks. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "Indtast stopsekvens",
|
||||
"Enter system prompt": "Indtast systemprompt",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Indtast Tavily API-nøgle",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "Indtast Tika Server URL",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Indtast din e-mail",
|
||||
"Enter Your Full Name": "Indtast dit fulde navn",
|
||||
"Enter your message": "Indtast din besked",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Indtast din adgangskode",
|
||||
"Enter Your Role": "Indtast din rolle",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Eksperimentel",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"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 connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "Kunne ikke opdatere indstillinger",
|
||||
"Failed to upload file.": "Kunne ikke uploade fil.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "Formular",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Hyppighedsstraf",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Lad stå tomt for ubegrænset",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Åbn fil",
|
||||
"Open in full screen": "Åbn i fuld skærm",
|
||||
"Open new chat": "Åbn ny chat",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI-version (v{{OPEN_WEBUI_VERSION}}) er lavere end den krævede version (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI API-nøgle er påkrævet.",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "OpenAI URL/nøgle påkrævet.",
|
||||
"openapi.json Path": "",
|
||||
"or": "eller",
|
||||
"Organize your users": "",
|
||||
"Other": "Andet",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Gennemgå omhyggeligt følgende advarsler:",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "Udfyld alle felter.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Tænker...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Denne handling kan ikke fortrydes. Vil du fortsætte?",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dette sikrer, at dine værdifulde samtaler gemmes sikkert i din backend-database. Tak!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dette er en eksperimentel funktion, den fungerer muligvis ikke som forventet og kan ændres når som helst.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "Værktøj importeret.",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Værktøj opdateret.",
|
||||
"Tools": "Værktøjer",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Version",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} af {{totalVersions}}",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "Stemme",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "Webhook-URL",
|
||||
"WebUI Settings": "WebUI-indstillinger",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "Nyheder i",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(neueste)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ Modelle }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "{{COUNT}} versteckte Zeilen",
|
||||
"{{COUNT}} Replies": "{{COUNT}} Antworten",
|
||||
"{{user}}'s Chats": "{{user}}s Chats",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "Attribut für Benutzername",
|
||||
"Audio": "Audio",
|
||||
"August": "August",
|
||||
"Auth": "",
|
||||
"Authenticate": "Authentifizieren",
|
||||
"Authentication": "Authentifizierung",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Antwort automatisch in die Zwischenablage kopieren",
|
||||
"Auto-playback response": "Antwort automatisch abspielen",
|
||||
"Autocomplete Generation": "Automatische Vervollständigung",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111-Basis-URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111-Basis-URL ist erforderlich.",
|
||||
"Available list": "Verfügbare Liste",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "Verfügbar!",
|
||||
"Awful": "Schrecklich",
|
||||
"Azure AI Speech": "Azure AI Speech",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "Neues Passwort bestätigen",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "Verbinden Sie sich zu Ihren OpenAI-kompatiblen Endpunkten.",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Verbindungen",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "Kontaktieren Sie den Administrator für den Zugriff auf die Weboberfläche",
|
||||
"Content": "Info",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "Direktverbindungen",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Direktverbindungen ermöglichen es Benutzern, sich mit ihren eigenen OpenAI-kompatiblen API-Endpunkten zu verbinden.",
|
||||
"Direct Connections settings updated": "Direktverbindungs-Einstellungen aktualisiert",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Deaktiviert",
|
||||
"Discover a function": "Entdecken Sie weitere Funktionen",
|
||||
"Discover a model": "Entdecken Sie weitere Modelle",
|
||||
@@ -379,7 +385,7 @@
|
||||
"Enable Mirostat sampling for controlling perplexity.": "",
|
||||
"Enable New Sign Ups": "Registrierung erlauben",
|
||||
"Enabled": "Aktiviert",
|
||||
"Enforce Temporary Chat": "",
|
||||
"Enforce Temporary Chat": "Temporären Chat erzwingen",
|
||||
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Stellen Sie sicher, dass Ihre CSV-Datei 4 Spalten in dieser Reihenfolge enthält: Name, E-Mail, Passwort, Rolle.",
|
||||
"Enter {{role}} message here": "Geben Sie die {{role}}-Nachricht hier ein",
|
||||
"Enter a detail about yourself for your LLMs to recall": "Geben Sie ein Detail über sich selbst ein, das Ihre Sprachmodelle (LLMs) sich merken sollen",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "Geben Sie den Kagi Search API-Schlüssel ein",
|
||||
"Enter Key Behavior": "Verhalten von 'Enter'",
|
||||
"Enter language codes": "Geben Sie die Sprachcodes ein",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Geben Sie die Modell-ID ein",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Geben Sie den Model-Tag ein",
|
||||
"Enter Mojeek Search API Key": "Geben Sie den Mojeek Search API-Schlüssel ein",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "Geben Sie den Server-Port ein",
|
||||
"Enter stop sequence": "Stop-Sequenz eingeben",
|
||||
"Enter system prompt": "Systemprompt eingeben",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Geben sie die öffentliche URL Ihrer WebUI ein. Diese URL wird verwendet, um Links in den Benachrichtigungen zu generieren.",
|
||||
"Enter Tika Server URL": "Geben Sie die Tika-Server-URL ein",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"Enter Your Full Name": "Geben Sie Ihren vollständigen Namen ein",
|
||||
"Enter your message": "Geben Sie Ihre Nachricht ein",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "Geben Sie Ihr neues Passwort ein",
|
||||
"Enter Your Password": "Geben Sie Ihr Passwort ein",
|
||||
"Enter Your Role": "Geben Sie Ihre Rolle ein",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "Ausschließen",
|
||||
"Execute code for analysis": "Code für Analyse ausführen",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "Aufklappen",
|
||||
"Experimental": "Experimentell",
|
||||
"Explain": "Erklären",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "Fehler beim Erstellen des API-Schlüssels.",
|
||||
"Failed to fetch models": "Fehler beim Abrufen der Modelle",
|
||||
"Failed to read clipboard contents": "Fehler beim Abruf der Zwischenablage",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "Fehler beim Speichern der Modellkonfiguration",
|
||||
"Failed to update settings": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"Failed to upload file.": "Fehler beim Hochladen der Datei.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "Neue Wege beschreiten",
|
||||
"Form": "Formular",
|
||||
"Format your variables using brackets like this:": "Formatieren Sie Ihre Variablen mit Klammern, wie hier:",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Frequenzstrafe",
|
||||
"Full Context Mode": "Voll-Kontext Modus",
|
||||
"Function": "Funktion",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "LDAP",
|
||||
"LDAP server updated": "LDAP-Server aktualisiert",
|
||||
"Leaderboard": "Bestenliste",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Leer lassen für unbegrenzt",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Datei öffnen",
|
||||
"Open in full screen": "Im Vollbildmodus öffnen",
|
||||
"Open new chat": "Neuen Chat öffnen",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "Open WebUI verwendet intern faster-whisper.",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI verwendet SpeechT5 und CMU Arctic-Sprecher-Embeddings.",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Die installierte Open-WebUI-Version (v{{OPEN_WEBUI_VERSION}}) ist niedriger als die erforderliche Version (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI-API-Schlüssel erforderlich.",
|
||||
"OpenAI API settings updated": "OpenAI-API-Einstellungen aktualisiert",
|
||||
"OpenAI URL/Key required.": "OpenAI-URL/Schlüssel erforderlich.",
|
||||
"openapi.json Path": "",
|
||||
"or": "oder",
|
||||
"Organize your users": "Organisieren Sie Ihre Benutzer",
|
||||
"Other": "Andere",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Bitte überprüfen Sie die folgenden Warnungen sorgfältig:",
|
||||
"Please do not close the settings page while loading the model.": "Bitte schließen die Einstellungen-Seite nicht, während das Modell lädt.",
|
||||
"Please enter a prompt": "Bitte geben Sie einen Prompt ein",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "Bitte füllen Sie alle Felder aus.",
|
||||
"Please select a model first.": "Bitte wählen Sie zuerst ein Modell aus.",
|
||||
"Please select a model.": "Bitte wählen Sie ein Modell aus.",
|
||||
@@ -1021,7 +1039,7 @@
|
||||
"Tell us more:": "Erzähl uns mehr",
|
||||
"Temperature": "Temperatur",
|
||||
"Template": "Vorlage",
|
||||
"Temporary Chat": "Temporärer Chat",
|
||||
"Temporary Chat": "Temporäre Unterhaltung",
|
||||
"Text Splitter": "Text-Splitter",
|
||||
"Text-to-Speech Engine": "Text-zu-Sprache-Engine",
|
||||
"Tfs Z": "Tfs Z",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Denke nach...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Dieser Kanal wurde am {{createdAt}} erstellt. Dies ist der Beginn des {{channelName}} Kanals.",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "Diese Unterhaltung erscheint nicht in deinem Chat-Verlauf. Alle Nachrichten sind privat und werden nicht gespeichert.",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Chats sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "Werkzeug-ID",
|
||||
"Tool imported successfully": "Werkzeug erfolgreich importiert",
|
||||
"Tool Name": "Werkzeugname",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Werkzeug erfolgreich aktualisiert",
|
||||
"Tools": "Werkzeuge",
|
||||
"Tools Access": "Werkzeugzugriff",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Version",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Version {{selectedVersion}} von {{totalVersions}}",
|
||||
"View Replies": "Antworten anzeigen",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "Sichtbarkeit",
|
||||
"Voice": "Stimme",
|
||||
"Voice Input": "Spracheingabe",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "Webhook URL",
|
||||
"WebUI Settings": "WebUI-Einstellungen",
|
||||
"WebUI URL": "WebUI-URL",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI wird Anfragen an \"{{url}}/api/chat\" senden",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI wird Anfragen an \"{{url}}/chat/completions\" senden",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "Was versuchen Sie zu erreichen?",
|
||||
"What are you working on?": "Woran arbeiten Sie?",
|
||||
"What’s New in": "Neuigkeiten von",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(much latest)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "Audio",
|
||||
"August": "",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Copy Bark Auto Bark",
|
||||
"Auto-playback response": "Auto-playback response",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Base URL",
|
||||
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Base URL is required.",
|
||||
"Available list": "",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "available! So excite!",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Connections",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "Content",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "",
|
||||
"Discover a function": "",
|
||||
"Discover a model": "",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Enter model doge tag (e.g. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "Enter stop bark",
|
||||
"Enter system prompt": "",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Enter Your Dogemail",
|
||||
"Enter Your Full Name": "Enter Your Full Wow",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Enter Your Barkword",
|
||||
"Enter Your Role": "",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Much Experiment",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Failed to read clipboard borks",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
"Failed to upload file.": "",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"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.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "",
|
||||
"Open in full screen": "",
|
||||
"Open new chat": "Open new bark",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "OpenAI Bark Key is required.",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "",
|
||||
"openapi.json Path": "",
|
||||
"or": "or",
|
||||
"Organize your users": "",
|
||||
"Other": "",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "",
|
||||
"This action cannot be undone. Do you wish to continue?": "",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "This ensures that your valuable conversations are securely saved to your backend database. Thank you! Much secure!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "",
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Version much version",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "",
|
||||
"WebUI Settings": "WebUI Settings much settings",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "What’s New in much new",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "(τελευταίο)",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "{{ models }}",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "Συνομιλίες του {{user}}",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "Ιδιότητα για Όνομα Χρήστη",
|
||||
"Audio": "Ήχος",
|
||||
"August": "Αύγουστος",
|
||||
"Auth": "",
|
||||
"Authenticate": "Επαλήθευση",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "Αυτόματη Αντιγραφή Απάντησης στο Πρόχειρο",
|
||||
"Auto-playback response": "Αυτόματη αναπαραγωγή της απάντησης",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "Βασικό URL AUTOMATIC1111",
|
||||
"AUTOMATIC1111 Base URL is required.": "Απαιτείται το Βασικό URL AUTOMATIC1111.",
|
||||
"Available list": "Διαθέσιμη λίστα",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "διαθέσιμο!",
|
||||
"Awful": "Ασχημο",
|
||||
"Azure AI Speech": "Ομιλία Azure AI",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "Συνδέσεις",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "Επικοινωνήστε με τον Διαχειριστή για Πρόσβαση στο WebUI",
|
||||
"Content": "Περιεχόμενο",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "Απενεργοποιημένο",
|
||||
"Discover a function": "Ανακάλυψη λειτουργίας",
|
||||
"Discover a model": "Ανακάλυψη μοντέλου",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "Εισάγετε κωδικούς γλώσσας",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "Εισάγετε το ID Μοντέλου",
|
||||
"Enter model tag (e.g. {{modelTag}})": "Εισάγετε την ετικέτα μοντέλου (π.χ. {{modelTag}})",
|
||||
"Enter Mojeek Search API Key": "Εισάγετε το Κλειδί API Mojeek Search",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "Εισάγετε την θύρα διακομιστή",
|
||||
"Enter stop sequence": "Εισάγετε τη σειρά παύσης",
|
||||
"Enter system prompt": "Εισάγετε την προτροπή συστήματος",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "Εισάγετε το Κλειδί API Tavily",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "Εισάγετε το URL διακομιστή Tika",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "Εισάγετε το Email σας",
|
||||
"Enter Your Full Name": "Εισάγετε το Πλήρες Όνομά σας",
|
||||
"Enter your message": "Εισάγετε το μήνυμά σας",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "Εισάγετε τον Κωδικό σας",
|
||||
"Enter Your Role": "Εισάγετε τον Ρόλο σας",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "Εξαίρεση",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "Πειραματικό",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "Αποτυχία δημιουργίας Κλειδιού API.",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "Αποτυχία ανάγνωσης περιεχομένων πρόχειρου",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "Αποτυχία αποθήκευσης ρυθμίσεων μοντέλων",
|
||||
"Failed to update settings": "Αποτυχία ενημέρωσης ρυθμίσεων",
|
||||
"Failed to upload file.": "Αποτυχία ανεβάσματος αρχείου.",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "Δημιουργήστε νέες διαδρομές",
|
||||
"Form": "Φόρμα",
|
||||
"Format your variables using brackets like this:": "Μορφοποιήστε τις μεταβλητές σας χρησιμοποιώντας αγκύλες όπως αυτό:",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "Ποινή Συχνότητας",
|
||||
"Full Context Mode": "",
|
||||
"Function": "Λειτουργία",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "LDAP",
|
||||
"LDAP server updated": "Ο διακομιστής LDAP ενημερώθηκε",
|
||||
"Leaderboard": "Κατάταξη",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "Αφήστε κενό για απεριόριστο",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Model": "Μοντέλο",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "Το μοντέλο '{{modelName}}' κατεβάστηκε με επιτυχία.",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "Το μοντέλο '{{modelTag}}' βρίσκεται ήδη στην ουρά για λήψη.",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "Άνοιγμα αρχείου",
|
||||
"Open in full screen": "Άνοιγμα σε πλήρη οθόνη",
|
||||
"Open new chat": "Άνοιγμα νέας συνομιλίας",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "Το Open WebUI χρησιμοποιεί το faster-whisper εσωτερικά.",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Το Open WebUI χρησιμοποιεί τα SpeechT5 και CMU Arctic embeddings ομιλητών.",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Η έκδοση Open WebUI (v{{OPEN_WEBUI_VERSION}}) είναι χαμηλότερη από την απαιτούμενη έκδοση (v{{REQUIRED_VERSION}})",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "Απαιτείται το Κλειδί API OpenAI.",
|
||||
"OpenAI API settings updated": "Οι ρυθμίσεις API OpenAI ενημερώθηκαν",
|
||||
"OpenAI URL/Key required.": "Απαιτείται URL/Kλειδί OpenAI.",
|
||||
"openapi.json Path": "",
|
||||
"or": "ή",
|
||||
"Organize your users": "Οργανώστε τους χρήστες σας",
|
||||
"Other": "Άλλο",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "Παρακαλώ αναθεωρήστε προσεκτικά τις ακόλουθες προειδοποιήσεις:",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "Παρακαλώ εισάγετε μια προτροπή",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "Παρακαλώ συμπληρώστε όλα τα πεδία.",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "Σκέφτομαι...",
|
||||
"This action cannot be undone. Do you wish to continue?": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί. Θέλετε να συνεχίσετε;",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Αυτό διασφαλίζει ότι οι πολύτιμες συνομιλίες σας αποθηκεύονται με ασφάλεια στη βάση δεδομένων backend σας. Ευχαριστούμε!",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Αυτή είναι μια πειραματική λειτουργία, μπορεί να μην λειτουργεί όπως αναμένεται και υπόκειται σε αλλαγές οποιαδήποτε στιγμή.",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "ID Εργαλείου",
|
||||
"Tool imported successfully": "Το εργαλείο εισήχθη με επιτυχία",
|
||||
"Tool Name": "Όνομα Εργαλείου",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "Το εργαλείο ενημερώθηκε με επιτυχία",
|
||||
"Tools": "Εργαλεία",
|
||||
"Tools Access": "Πρόσβαση Εργαλείων",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "Έκδοση",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "Έκδοση {{selectedVersion}} από {{totalVersions}}",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "Ορατότητα",
|
||||
"Voice": "Φωνή",
|
||||
"Voice Input": "Εισαγωγή Φωνής",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "URL Webhook",
|
||||
"WebUI Settings": "Ρυθμίσεις WebUI",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/api/chat\"",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/chat/completions\"",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "Τι προσπαθείτε να πετύχετε?",
|
||||
"What are you working on?": "Τι εργάζεστε;",
|
||||
"What’s New in": "Τι νέο υπάρχει στο",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"(latest)": "",
|
||||
"(Ollama)": "",
|
||||
"{{ models }}": "",
|
||||
"{{COUNT}} Available Tool Servers": "",
|
||||
"{{COUNT}} Available Tools": "",
|
||||
"{{COUNT}} hidden lines": "",
|
||||
"{{COUNT}} Replies": "",
|
||||
"{{user}}'s Chats": "",
|
||||
@@ -108,8 +108,10 @@
|
||||
"Attribute for Username": "",
|
||||
"Audio": "",
|
||||
"August": "",
|
||||
"Auth": "",
|
||||
"Authenticate": "",
|
||||
"Authentication": "",
|
||||
"Auto": "",
|
||||
"Auto-Copy Response to Clipboard": "",
|
||||
"Auto-playback response": "",
|
||||
"Autocomplete Generation": "",
|
||||
@@ -119,7 +121,7 @@
|
||||
"AUTOMATIC1111 Base URL": "",
|
||||
"AUTOMATIC1111 Base URL is required.": "",
|
||||
"Available list": "",
|
||||
"Available Tool Servers": "",
|
||||
"Available Tools": "",
|
||||
"available!": "",
|
||||
"Awful": "",
|
||||
"Azure AI Speech": "",
|
||||
@@ -218,7 +220,10 @@
|
||||
"Confirm your new password": "",
|
||||
"Connect to your own OpenAI compatible API endpoints.": "",
|
||||
"Connect to your own OpenAPI compatible external tool servers.": "",
|
||||
"Connection failed": "",
|
||||
"Connection successful": "",
|
||||
"Connections": "",
|
||||
"Connections saved successfully": "",
|
||||
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "",
|
||||
"Contact Admin for WebUI Access": "",
|
||||
"Content": "",
|
||||
@@ -303,6 +308,7 @@
|
||||
"Direct Connections": "",
|
||||
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "",
|
||||
"Direct Connections settings updated": "",
|
||||
"Direct Tool Servers": "",
|
||||
"Disabled": "",
|
||||
"Discover a function": "",
|
||||
"Discover a model": "",
|
||||
@@ -412,6 +418,7 @@
|
||||
"Enter Kagi Search API Key": "",
|
||||
"Enter Key Behavior": "",
|
||||
"Enter language codes": "",
|
||||
"Enter Mistral API Key": "",
|
||||
"Enter Model ID": "",
|
||||
"Enter model tag (e.g. {{modelTag}})": "",
|
||||
"Enter Mojeek Search API Key": "",
|
||||
@@ -436,6 +443,7 @@
|
||||
"Enter server port": "",
|
||||
"Enter stop sequence": "",
|
||||
"Enter system prompt": "",
|
||||
"Enter system prompt here": "",
|
||||
"Enter Tavily API Key": "",
|
||||
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
|
||||
"Enter Tika Server URL": "",
|
||||
@@ -449,6 +457,7 @@
|
||||
"Enter Your Email": "",
|
||||
"Enter Your Full Name": "",
|
||||
"Enter your message": "",
|
||||
"Enter your name": "",
|
||||
"Enter your new password": "",
|
||||
"Enter Your Password": "",
|
||||
"Enter Your Role": "",
|
||||
@@ -468,7 +477,7 @@
|
||||
"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "",
|
||||
"Exclude": "",
|
||||
"Execute code for analysis": "",
|
||||
"Executing `{{NAME}}`...": "",
|
||||
"Executing **{{NAME}}**...": "",
|
||||
"Expand": "",
|
||||
"Experimental": "",
|
||||
"Explain": "",
|
||||
@@ -493,6 +502,7 @@
|
||||
"Failed to create API Key.": "",
|
||||
"Failed to fetch models": "",
|
||||
"Failed to read clipboard contents": "",
|
||||
"Failed to save connections": "",
|
||||
"Failed to save models configuration": "",
|
||||
"Failed to update settings": "",
|
||||
"Failed to upload file.": "",
|
||||
@@ -525,6 +535,7 @@
|
||||
"Forge new paths": "",
|
||||
"Form": "",
|
||||
"Format your variables using brackets like this:": "",
|
||||
"Forwards system user session credentials to authenticate": "",
|
||||
"Frequency Penalty": "",
|
||||
"Full Context Mode": "",
|
||||
"Function": "",
|
||||
@@ -644,6 +655,7 @@
|
||||
"LDAP": "",
|
||||
"LDAP server updated": "",
|
||||
"Leaderboard": "",
|
||||
"Learn more about OpenAPI tool servers.": "",
|
||||
"Leave empty for unlimited": "",
|
||||
"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
|
||||
"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
|
||||
@@ -694,6 +706,8 @@
|
||||
"Mirostat": "",
|
||||
"Mirostat Eta": "",
|
||||
"Mirostat Tau": "",
|
||||
"Mistral OCR": "",
|
||||
"Mistral OCR API Key required.": "",
|
||||
"Model": "",
|
||||
"Model '{{modelName}}' has been successfully downloaded.": "",
|
||||
"Model '{{modelTag}}' is already in queue for downloading.": "",
|
||||
@@ -780,6 +794,7 @@
|
||||
"Open file": "",
|
||||
"Open in full screen": "",
|
||||
"Open new chat": "",
|
||||
"Open WebUI can use tools provided by any OpenAPI server.": "",
|
||||
"Open WebUI uses faster-whisper internally.": "",
|
||||
"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "",
|
||||
"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
|
||||
@@ -789,6 +804,7 @@
|
||||
"OpenAI API Key is required.": "",
|
||||
"OpenAI API settings updated": "",
|
||||
"OpenAI URL/Key required.": "",
|
||||
"openapi.json Path": "",
|
||||
"or": "",
|
||||
"Organize your users": "",
|
||||
"Other": "",
|
||||
@@ -820,6 +836,8 @@
|
||||
"Please carefully review the following warnings:": "",
|
||||
"Please do not close the settings page while loading the model.": "",
|
||||
"Please enter a prompt": "",
|
||||
"Please enter a valid path": "",
|
||||
"Please enter a valid URL": "",
|
||||
"Please fill in all fields.": "",
|
||||
"Please select a model first.": "",
|
||||
"Please select a model.": "",
|
||||
@@ -1042,6 +1060,7 @@
|
||||
"Thinking...": "",
|
||||
"This action cannot be undone. Do you wish to continue?": "",
|
||||
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
|
||||
"This chat won’t appear in history and your messages will not be saved.": "",
|
||||
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "",
|
||||
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "",
|
||||
@@ -1089,6 +1108,7 @@
|
||||
"Tool ID": "",
|
||||
"Tool imported successfully": "",
|
||||
"Tool Name": "",
|
||||
"Tool Servers": "",
|
||||
"Tool updated successfully": "",
|
||||
"Tools": "",
|
||||
"Tools Access": "",
|
||||
@@ -1158,7 +1178,7 @@
|
||||
"Version": "",
|
||||
"Version {{selectedVersion}} of {{totalVersions}}": "",
|
||||
"View Replies": "",
|
||||
"View Result from `{{NAME}}`": "",
|
||||
"View Result from **{{NAME}}**": "",
|
||||
"Visibility": "",
|
||||
"Voice": "",
|
||||
"Voice Input": "",
|
||||
@@ -1176,9 +1196,9 @@
|
||||
"Webhook URL": "",
|
||||
"WebUI Settings": "",
|
||||
"WebUI URL": "",
|
||||
"WebUI will make requests to \"{{url}}\"": "",
|
||||
"WebUI will make requests to \"{{url}}/api/chat\"": "",
|
||||
"WebUI will make requests to \"{{url}}/chat/completions\"": "",
|
||||
"WebUI will make requests to \"{{url}}/openapi.json\"": "",
|
||||
"What are you trying to achieve?": "",
|
||||
"What are you working on?": "",
|
||||
"What’s New in": "",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user