From 5944eda0ff25a284f7157252683bccede741cbe7 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 15 Apr 2026 10:17:40 -0700 Subject: [PATCH 001/119] refac --- backend/open_webui/utils/images/comfyui.py | 204 ++++++++++----------- 1 file changed, 94 insertions(+), 110 deletions(-) diff --git a/backend/open_webui/utils/images/comfyui.py b/backend/open_webui/utils/images/comfyui.py index 497808c22d..9172f1c325 100644 --- a/backend/open_webui/utils/images/comfyui.py +++ b/backend/open_webui/utils/images/comfyui.py @@ -1,49 +1,51 @@ -import asyncio import json import logging import random -import requests -import aiohttp import urllib.parse -import urllib.request from typing import Optional -import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client) +import aiohttp from pydantic import BaseModel +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL +from open_webui.utils.session_pool import get_session + log = logging.getLogger(__name__) default_headers = {'User-Agent': 'Mozilla/5.0'} -def queue_prompt(prompt, client_id, base_url, api_key): +async def queue_prompt(prompt, client_id, base_url, api_key): log.info('queue_prompt') p = {'prompt': prompt, 'client_id': client_id} - data = json.dumps(p).encode('utf-8') - log.debug(f'queue_prompt data: {data}') + log.debug(f'queue_prompt data: {p}') try: - req = urllib.request.Request( + session = await get_session() + async with session.post( f'{base_url}/prompt', - data=data, + json=p, headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - response = urllib.request.urlopen(req).read() - return json.loads(response) + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.json() except Exception as e: log.exception(f'Error while queuing prompt: {e}') - raise e + raise -def get_image(filename, subfolder, folder_type, base_url, api_key): +async def get_image(filename, subfolder, folder_type, base_url, api_key): log.info('get_image') data = {'filename': filename, 'subfolder': subfolder, 'type': folder_type} url_values = urllib.parse.urlencode(data) - req = urllib.request.Request( + session = await get_session() + async with session.get( f'{base_url}/view?{url_values}', headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - with urllib.request.urlopen(req) as response: - return response.read() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.read() def get_image_url(filename, subfolder, folder_type, base_url): @@ -53,32 +55,39 @@ def get_image_url(filename, subfolder, folder_type, base_url): return f'{base_url}/view?{url_values}' -def get_history(prompt_id, base_url, api_key): +async def get_history(prompt_id, base_url, api_key): log.info('get_history') - - req = urllib.request.Request( + session = await get_session() + async with session.get( f'{base_url}/history/{prompt_id}', headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - with urllib.request.urlopen(req) as response: - return json.loads(response.read()) + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.json() -def get_images(ws, workflow, client_id, base_url, api_key): - prompt_id = queue_prompt(workflow, client_id, base_url, api_key)['prompt_id'] +async def _ws_get_images(ws, workflow, client_id, base_url, api_key): + """Queue a prompt and wait on *ws* for ComfyUI to finish executing it. + + Returns a dict of ``{'data': [{'url': ...}, ...]}``. + """ + prompt_id = (await queue_prompt(workflow, client_id, base_url, api_key))['prompt_id'] output_images = [] - while True: - out = ws.recv() - if isinstance(out, str): - message = json.loads(out) + + async for msg in ws: + if msg.type == aiohttp.WSMsgType.TEXT: + message = json.loads(msg.data) if message['type'] == 'executing': data = message['data'] if data['node'] is None and data['prompt_id'] == prompt_id: break # Execution is done - else: - continue # previews are binary data + elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR): + log.error(f'WebSocket closed unexpectedly: {msg.type}') + break + # binary messages (previews) are silently skipped - history = get_history(prompt_id, base_url, api_key)[prompt_id] + history = (await get_history(prompt_id, base_url, api_key))[prompt_id] for node_id in history['outputs']: node_output = history['outputs'][node_id] if node_id in workflow and workflow[node_id].get('class_type') in [ @@ -105,10 +114,10 @@ async def comfyui_upload_image(image_file_item, base_url, api_key): form.add_field('image', file_bytes, filename=filename, content_type=mime_type) form.add_field('type', 'input') # required by ComfyUI - async with aiohttp.ClientSession() as session: - async with session.post(url, data=form, headers=headers) as resp: - resp.raise_for_status() - return await resp.json() + session = await get_session() + async with session.post(url, data=form, headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + resp.raise_for_status() + return await resp.json() class ComfyUINodeInput(BaseModel): @@ -136,11 +145,9 @@ class ComfyUICreateImageForm(BaseModel): seed: Optional[int] = None -async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key): - ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') - workflow = json.loads(payload.workflow.workflow) - - for node in payload.workflow.nodes: +def _apply_workflow_nodes(workflow, nodes, model, payload): + """Mutate *workflow* dict in-place based on typed node definitions.""" + for node in nodes: if node.type: if node.type == 'model': for node_id in node.node_ids: @@ -151,6 +158,14 @@ async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, clie elif node.type == 'negative_prompt': for node_id in node.node_ids: workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.negative_prompt + elif node.type == 'image': + if isinstance(payload.image, list): + for idx, node_id in enumerate(node.node_ids): + if idx < len(payload.image): + workflow[node_id]['inputs'][node.key] = payload.image[idx] + else: + for node_id in node.node_ids: + workflow[node_id]['inputs'][node.key] = payload.image elif node.type == 'width': for node_id in node.node_ids: workflow[node_id]['inputs'][node.key if node.key else 'width'] = payload.width @@ -171,24 +186,31 @@ async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, clie for node_id in node.node_ids: workflow[node_id]['inputs'][node.key] = node.value + +async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key): + ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') + workflow = json.loads(payload.workflow.workflow) + _apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload) + + headers = {'Authorization': f'Bearer {api_key}'} + session = await get_session() + try: - ws = websocket.WebSocket() - headers = {'Authorization': f'Bearer {api_key}'} - ws.connect(f'{ws_url}/ws?clientId={client_id}', header=headers) - log.info('WebSocket connection established.') - except Exception as e: + async with session.ws_connect( + f'{ws_url}/ws?clientId={client_id}', + headers=headers, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as ws: + log.info('WebSocket connection established.') + log.info('Sending workflow to WebSocket server.') + log.info(f'Workflow: {workflow}') + images = await _ws_get_images(ws, workflow, client_id, base_url, api_key) + except aiohttp.WSServerHandshakeError as e: log.exception(f'Failed to connect to WebSocket server: {e}') return None - - try: - log.info('Sending workflow to WebSocket server.') - log.info(f'Workflow: {workflow}') - images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url, api_key) except Exception as e: - log.exception(f'Error while receiving images: {e}') - images = None - - ws.close() + log.exception(f'Error during image generation: {e}') + return None return images @@ -209,64 +231,26 @@ class ComfyUIEditImageForm(BaseModel): async def comfyui_edit_image(model: str, payload: ComfyUIEditImageForm, client_id, base_url, api_key): ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') workflow = json.loads(payload.workflow.workflow) + _apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload) - for node in payload.workflow.nodes: - if node.type: - if node.type == 'model': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = model - elif node.type == 'image': - if isinstance(payload.image, list): - # check if multiple images are provided - for idx, node_id in enumerate(node.node_ids): - if idx < len(payload.image): - workflow[node_id]['inputs'][node.key] = payload.image[idx] - else: - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = payload.image - elif node.type == 'prompt': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.prompt - elif node.type == 'negative_prompt': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.negative_prompt - elif node.type == 'width': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'width'] = payload.width - elif node.type == 'height': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'height'] = payload.height - elif node.type == 'n': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'batch_size'] = payload.n - elif node.type == 'steps': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'steps'] = payload.steps - elif node.type == 'seed': - seed = payload.seed if payload.seed else random.randint(0, 1125899906842624) - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = seed - else: - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = node.value + headers = {'Authorization': f'Bearer {api_key}'} + session = await get_session() try: - ws = websocket.WebSocket() - headers = {'Authorization': f'Bearer {api_key}'} - ws.connect(f'{ws_url}/ws?clientId={client_id}', header=headers) - log.info('WebSocket connection established.') - except Exception as e: + async with session.ws_connect( + f'{ws_url}/ws?clientId={client_id}', + headers=headers, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as ws: + log.info('WebSocket connection established.') + log.info('Sending workflow to WebSocket server.') + log.info(f'Workflow: {workflow}') + images = await _ws_get_images(ws, workflow, client_id, base_url, api_key) + except aiohttp.WSServerHandshakeError as e: log.exception(f'Failed to connect to WebSocket server: {e}') return None - - try: - log.info('Sending workflow to WebSocket server.') - log.info(f'Workflow: {workflow}') - images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url, api_key) except Exception as e: - log.exception(f'Error while receiving images: {e}') - images = None - - ws.close() + log.exception(f'Error during image editing: {e}') + return None return images From e6297cf414cb984065f62cf2729e424f1f3c8d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Wed, 15 Apr 2026 18:18:55 +0100 Subject: [PATCH 002/119] i18n: Update Irish translation with new strings (#23748) Update Irish translation --- src/lib/i18n/locales/ie-GA/translation.json | 370 ++++++++++---------- 1 file changed, 184 insertions(+), 186 deletions(-) diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json index 2fa16b9a30..58788f792f 100644 --- a/src/lib/i18n/locales/ie-GA/translation.json +++ b/src/lib/i18n/locales/ie-GA/translation.json @@ -17,8 +17,8 @@ "{{COUNT}} members": "{{COUNT}} ball", "{{COUNT}} Replies": "{{COUNT}} Freagra", "{{COUNT}} Rows": "{{COUNT}} Sraitheanna", - "{{count}} selected_one": "", - "{{count}} selected_other": "", + "{{count}} selected_one": "{{count}} mir roghnaithe", + "{{count}} selected_other": "{{count}} míreanna roghnaithe", "{{COUNT}} Sources": "{{COUNT}} Foinsí", "{{COUNT}} words": "{{COUNT}} focail", "{{COUNT}}d_time_ago": "l", @@ -32,7 +32,7 @@ "{{NAMES}} reacted with {{REACTION}}": "D’fhreagair {{NAMES}} le {{REACTION}}", "{{user}}'s Chats": "Comhráite {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Ceoldeireadh Riachtanach", - "*Prompt node ID(s) are required for image generation": "* Tá ID nód leid ag teastáil chun íomhá a ghiniúint", + "*Prompt node ID(s) are required for image generation": "* Tá ID(anna) nód treorach ag teastáil chun íomhá a ghiniúint", "1 Source": "1 Foinse", "1m_time_ago": "1 nóiméad ó shin", "A collaboration channel where people join as members": "Cainéal comhoibrithe ina mbíonn daoine ag glacadh páirte mar bhaill", @@ -42,7 +42,7 @@ "A task model is used when performing tasks such as generating titles for chats and web search queries": "Úsáidtear samhail tascanna agus tascanna á ndéanamh amhail teidil a ghiniúint le haghaidh comhráite agus fiosrúcháin chuardaigh ghréasáin", "a user": "úsáideoir", "About": "Maidir", - "Accept Autocomplete Generation\nJump to Prompt Variable": "Glac le Giniúint Uathchríochnaithe\nLéim go dtí an Athróg Pras", + "Accept Autocomplete Generation\nJump to Prompt Variable": "Glac le Giniúint Uathchríochnaithe\nLéim go dtí Athróg na Treorach", "Access": "Rochtain", "Access Control": "Rialaithe Rochtana", "Access Grants": "Deontais Rochtana", @@ -71,7 +71,7 @@ "Add Content": "Cuir Ábhar leis", "Add content here": "Cuir ábhar anseo", "Add Custom Parameter": "Cuir Paraiméadar Saincheaptha leis", - "Add Custom Prompt": "Cuir Leid Saincheaptha leis", + "Add Custom Prompt": "Cuir Treoir Shaincheaptha leis", "Add Details": "Cuir Sonraí leis", "Add Files": "Cuir Comhaid", "Add Image": "Cuir Íomhá leis", @@ -122,14 +122,14 @@ "Allow Chat Export": "Ceadaigh Easpórtáil Comhrá", "Allow Chat Params": "Ceadaigh Paraiméadair Comhrá", "Allow Chat Share": "Ceadaigh Comhroinnt Comhrá", - "Allow Chat System Prompt": "Ceadaigh Pras Córais Comhrá", + "Allow Chat System Prompt": "Ceadaigh Treoir Chórais Comhrá", "Allow Chat Valves": "Ceadaigh Comhlaí Comhrá", "Allow Continue Response": "Ceadaigh Leanúint ar aghaidh leis an bhFreagra", "Allow Delete Messages": "Ceadaigh Teachtaireachtaí a Scriosadh", "Allow File Upload": "Ceadaigh Uaslódáil Comhad", "Allow Multiple Models in Chat": "Ceadaigh Il-Samhlacha i gComhrá", - "Allow non-local voices": "Lig guthanna neamh-áitiúla", - "Allow public write access": "", + "Allow non-local voices": "Ceadaigh guthanna neamh-áitiúla", + "Allow public write access": "Ceadaigh rochtain scríbhneoireachta phoiblí", "Allow Rate Response": "Ceadaigh Freagairt Ráta", "Allow Regenerate Response": "Ceadaigh Freagra Athghiniúint", "Allow Sharing With Users": "Ceadaigh Comhroinnt le hÚsáideoirí", @@ -182,14 +182,14 @@ "Are you sure you want to archive all chats? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat na comhráite go léir a chartlannú? Ní féidir an gníomh seo a chealú.", "Are you sure you want to clear all memories? This action cannot be undone.": "An bhfuil tú cinnte gur mhaith leat na cuimhní go léir a ghlanadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete \"{{NAME}}\"?": "An bhfuil tú cinnte gur mian leat \"{{NAME}}\" a scriosadh?", - "Are you sure you want to delete **{{modelName}}**?": "", + "Are you sure you want to delete **{{modelName}}**?": "An bhfuil tú cinnte gur mian leat **{{modelName}}** a scriosadh?", "Are you sure you want to delete all chats? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat na comhráite go léir a scriosadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete this channel?": "An bhfuil tú cinnte gur mhaith leat an cainéal seo a scriosadh?", - "Are you sure you want to delete this connection? This action cannot be undone.": "", - "Are you sure you want to delete this memory? This action cannot be undone.": "", + "Are you sure you want to delete this connection? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat an nasc seo a scriosadh? Ní féidir an gníomh seo a chealú.", + "Are you sure you want to delete this memory? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat an chuimhne seo a scriosadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete this message?": "An bhfuil tú cinnte gur mhaith leat an teachtaireacht seo a scriosadh?", "Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "An bhfuil tú cinnte gur mian leat an leagan seo a scriosadh? Déanfar leaganacha linbh a athnascadh le tuismitheoir an leagain seo.", - "Are you sure you want to delete this?": "", + "Are you sure you want to delete this?": "An bhfuil tú cinnte gur mian leat é seo a scriosadh?", "Are you sure you want to unarchive all archived chats?": "An bhfuil tú cinnte gur mhaith leat gach comhrá cartlainne a dhíchartlannú?", "Arena Models": "Samhlacha Réimse", "Artifacts": "Déantáin", @@ -199,7 +199,7 @@ "Assistant": "Cúntóir", "Async Embedding Processing": "Próiseáil Leabaithe Asyncrónach", "Attach File From Knowledge": "Ceangail Comhad ó Eolas", - "Attach Files": "", + "Attach Files": "Ceangail Comhaid", "Attach Knowledge": "Ceangail Eolas", "Attach Notes": "Ceangail Nótaí", "Attach Webpage": "Ceangail Leathanach Gréasáin", @@ -222,13 +222,13 @@ "AUTOMATIC1111 Base URL": "UATHOIBRÍOCH1111 Bun URL", "AUTOMATIC1111 Base URL is required.": "Tá URL bonn UATHOIBRÍOCH1111 ag teastáil.", "Automatically inject system tools in native function calling mode (e.g., timestamps, memory, chat history, notes, etc.)": "Uirlisí córais a instealladh go huathoibríoch i mód glaonna feidhme dúchais (m.sh., stampaí ama, cuimhne, stair comhrá, nótaí, srl.)", - "Automation": "", - "Automation created": "", - "Automation Name": "", - "Automation title": "", - "Automation triggered": "", - "Automation updated": "", - "Automations": "", + "Automation": "Uathoibriú", + "Automation created": "Uathoibriú cruthaithe", + "Automation Name": "Ainm uathoibrithe", + "Automation title": "Teideal uathoibrithe", + "Automation triggered": "Uathoibriú spreagtha", + "Automation updated": "Uathoibriú nuashonraithe", + "Automations": "Uathoibrithe", "Available list": "Liosta atá ar fáil", "Available models": "Samhlacha atá ar fáil", "Available Tools": "Uirlisí ar Fáil", @@ -259,13 +259,13 @@ "Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "Treisiú nó pionós a ghearradh ar chomharthaí sonracha as freagraí srianta. Déanfar luachanna laofachta a chlampáil idir -100 agus 100 (san áireamh). (Réamhshocrú: ceann ar bith)", "Brave": "Brave", "Brave Search API Key": "Eochair API Cuardaigh Brave", - "Break down complex requests into trackable steps": "", + "Break down complex requests into trackable steps": "Bris síos iarratais chasta i gcéimeanna inrianaithe", "Browse and query knowledge bases": "Brabhsáil agus fiosraigh bunachair eolais", "Builtin Tools": "Uirlisí Tógtha", "Bullet List": "Liosta Urchair", "Button ID": "Aitheantas an Chnaipe", "Button Label": "Lipéad Cnaipe", - "Button Prompt": "Leid Cnaipe", + "Button Prompt": "Treoir Cnaipe", "by {{name}}": "le {{name}}", "By {{name}}": "Le {{name}}", "Bypass Embedding and Retrieval": "Seachbhóthar Leabú agus Aisghabháil", @@ -298,7 +298,7 @@ "Character limit for autocomplete generation input": "Teorainn charachtair le haghaidh ionchur giniúna uathchríochnaithe", "Chart new frontiers": "Cairt teorainneacha nua", "Chat": "Comhrá", - "Chat archived.": "", + "Chat archived.": "Comhrá cartlannaithe.", "Chat Background Image": "Íomhá Cúlra Comhrá", "Chat Bubble UI": "Comhrá Bubble UI", "Chat Completions": "Críochnuithe Comhrá", @@ -341,10 +341,10 @@ "Click here to upload a workflow.json file.": "Cliceáil anseo chun comhad workflow.json a uaslódáil.", "click here.": "cliceáil anseo.", "Click on the user role button to change a user's role.": "Cliceáil ar an gcnaipe ról úsáideora chun ról úsáideora a athrú.", - "Click to connect": "", + "Click to connect": "Cliceáil chun ceangal", "Click to copy ID": "Cliceáil chun an t-aitheantas a chóipeáil", - "Client ID": "", - "Client Secret": "", + "Client ID": "Aitheantas Cliant", + "Client Secret": "Rún an Chliaint", "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Diúltaíodh cead scríofa an ghearrthaisce. Seiceáil socruithe do bhrabhsálaí chun an rochtain riachtanach a dheonú.", "Clone": "Clón", "Clone Chat": "Comhrá Clón", @@ -370,7 +370,7 @@ "Code formatted successfully": "Cód formáidithe go rathúil", "Code Interpreter": "Ateangaire Cód", "Code Interpreter Engine": "Inneall Ateangaire Cóid", - "Code Interpreter Prompt Template": "Teimpléad Pras Ateangaire Cód", + "Code Interpreter Prompt Template": "Teimpléad Treorach Ateangaire Cód", "Collaboration channel where people join as members": "Cainéal comhoibrithe ina mbíonn daoine páirteach mar bhaill", "Collapse": "Laghdaigh", "Collection": "Bailiúchán", @@ -393,11 +393,11 @@ "Concurrent Requests": "Iarrataí Comhthéime", "Config": "Cumraíocht", "Config imported successfully": "Cumraíocht allmhairithe go rathúil", - "Configuration": "", + "Configuration": "Cumraíocht", "Configure": "Cumraigh", "Confirm": "Deimhnigh", "Confirm Password": "Deimhnigh Pasfhocal", - "Confirm Prompt from Embed": "", + "Confirm Prompt from Embed": "Deimhnigh Treoir ón Leabú", "Confirm your action": "Deimhnigh do ghníomh", "Confirm your new password": "Deimhnigh do phasfhocal nua", "Confirm Your Password": "Deimhnigh Do Phasfhocal", @@ -406,7 +406,7 @@ "Connect to Open Terminal instances. All users will have access to file browsing and terminal tools through these servers.": "Ceangail le cásanna Open Terminal. Beidh rochtain ag gach úsáideoir ar bhrabhsáil comhad agus uirlisí críochfoirt trí na freastalaithe seo.", "Connect to your own OpenAI compatible API endpoints.": "Ceangail le do chríochphointí API atá comhoiriúnach le OpenAI.", "Connect to your own OpenAPI compatible external tool servers.": "Ceangail le do fhreastalaithe uirlisí seachtracha atá comhoiriúnach le OpenAPI.", - "Connected ({{type}})": "", + "Connected ({{type}})": "Ceangailte ({{type}})", "Connection failed": "Theip ar an gceangal", "Connection successful": "Ceangal rathúil", "Connection Type": "Cineál Ceangail", @@ -439,7 +439,7 @@ "Copy Last Response": "Cóipeáil an Fhreagra Deiridh", "Copy link": "Cóipeáil nasc", "Copy Link": "Cóipeáil Nasc", - "Copy Prompt": "CCóipeáil an Treoir", + "Copy Prompt": "Cóipeáil an Treoir", "Copy Share Link": "Cóipeáil Nasc Comhroinnte", "Copy to clipboard": "Cóipeáil chuig an ngearrthaisce", "Copy Token": "Cóipeáil Comhartha", @@ -447,14 +447,14 @@ "Copying to clipboard was successful!": "D'éirigh le cóipeáil chuig an ngearrthaisce!", "CORS must be properly configured by the provider to allow requests from Open WebUI.": "Ní mór don soláthraí CORS a chumrú i gceart chun iarratais ó Open WebUI a cheadú.", "Could not read file.": "Níorbh fhéidir an comhad a léamh.", - "CPU": "", + "CPU": "LAP", "Create": "Cruthaigh", "Create a knowledge base": "Cruthaigh bonn eolais", "Create a model": "Cruthaigh samhail", "Create a new note": "Cruthaigh nóta nua", "Create Account": "Cruthaigh Cuntas", "Create Admin Account": "Cruthaigh Cuntas Riaracháin", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "Cruthaigh agus bainistigh uathoibrithe sceidealaithe", "Create Channel": "Cruthaigh Cainéal", "Create Folder": "Cruthaigh Fillteán", "Create Image": "Cruthaigh Íomhá", @@ -464,7 +464,7 @@ "Create new secret key": "Cruthaigh eochair rúnda nua", "Create note": "Cruthaigh nóta", "Create Note": "Cruthaigh Nóta", - "Create scheduled prompts that run automatically on a recurring basis.": "", + "Create scheduled prompts that run automatically on a recurring basis.": "Cruthaigh leideanna sceidealaithe a ritheann go huathoibríoch ar bhonn athfhillteach.", "Create your first note by clicking on the plus button below.": "Cruthaigh do chéad nóta trí chliceáil ar an gcnaipe móide thíos.", "Created at": "Cruthaithe ag", "Created At": "Cruthaithe Ag", @@ -480,14 +480,14 @@ "Custom Gender": "Inscne Saincheaptha", "Custom Parameter Name": "Ainm Paraiméadair Saincheaptha", "Custom Parameter Value": "Luach Paraiméadair Saincheaptha", - "Daily": "", + "Daily": "Laethúil", "Daily Messages": "Teachtaireachtaí Laethúla", "Danger Zone": "Crios Contúirte", "Dark": "Dorcha", "Data Controls": "Rialuithe Sonraí", "Database": "Bunachar Sonraí", "Datalab Marker API": "API Marcóra Datalab", - "Day": "", + "Day": "Lá", "DD/MM/YYYY": "DD/MM/YYYY", "DDGS Backend": "Cúltaca DDGS", "December": "Nollaig", @@ -506,30 +506,30 @@ "Default model updated": "Nuashonraithe samhail réamhshocraithe", "Default permissions": "Ceadanna réamhshocraithe", "Default permissions updated successfully": "D'éirigh le ceadanna réamhshocraithe a nuashonrú", - "Default Prompt Suggestions": "Moltaí Leid Réamhshocraithe", + "Default Prompt Suggestions": "Moltaí Treoracha Réamhshocraithe", "Default to 389 or 636 if TLS is enabled": "Réamhshocrú go 389 nó 636 má tá TLS cumasaithe", "Default to ALL": "Réamhshocrú do GACH", "Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.": "Réamhshocrú maidir le haisghabháil deighilte d'eastóscadh ábhar dírithe agus ábhartha, moltar é seo i bhformhór na gcásanna.", "Default User Role": "Ról Úsáideora Réamhshocraithe", "Defaults": "Réamhshocruithe", "Delete": "Scrios", - "Delete {{name}}": "", + "Delete {{name}}": "Scrios {{name}}", "Delete a model": "Scrios samhail", "Delete All": "Scrios Gach Rud", "Delete All Chats": "Scrios Gach Comhrá", "Delete all contents inside this folder": "Scrios an t-ábhar go léir atá sa fhillteán seo", - "Delete automation?": "", + "Delete automation?": "Scrios an t-uathoibriú?", "Delete Chat": "Scrios Comhrá", "Delete chat?": "Scrios comhrá?", "Delete File": "Scrios Comhad", "Delete folder?": "Scrios fillteán?", "Delete function?": "Scrios feidhm?", - "Delete Memory?": "", + "Delete Memory?": "Scrios Cuimhne?", "Delete Message": "Scrios Teachtaireacht", "Delete message?": "Scrios teachtaireacht?", "Delete Model": "Scrios an tSamhail", "Delete note?": "Scrios an nóta?", - "Delete prompt?": "Scrios leid?", + "Delete prompt?": "Scrios an treoir?", "Delete skill?": "Scrios an scil?", "delete this link": "scrios an nasc seo", "Delete tool?": "Uirlis a scriosadh?", @@ -538,7 +538,7 @@ "Deleted": "Scriosta", "Deleted {{deleteModelTag}}": "Scriosta {{deleteModelTag}}", "Deleted {{name}}": "Scriosta {{name}}", - "Deleted {{ok}} of {{total}} items": "", + "Deleted {{ok}} of {{total}} items": "Scriosadh {{ok}} de {{total}} míreanna", "Deleted User": "Úsáideoir Scriosta", "Deployment names are required for Azure OpenAI": "Tá ainmneacha imscartha ag teastáil le haghaidh Azure OpenAI", "Desc": "Cur Síos", @@ -547,7 +547,7 @@ "Describe what changed...": "Déan cur síos ar a bhfuil athraithe...", "Describe your knowledge base and objectives": "Déan cur síos ar do bhunachar eolais agus do chuspóirí", "Description": "Cur síos", - "Deselect": "", + "Deselect": "Díroghnaigh", "Detect Artifacts Automatically": "Déan Déantáin a bhrath go huathoibríoch", "Dictate": "Deachtaigh", "Didn't fully follow instructions": "Níor lean sé treoracha go hiomlán", @@ -564,14 +564,14 @@ "Disabled": "Díchumasaithe", "Discover a function": "Faigh amach feidhm", "Discover a model": "Faigh amach samhail", - "Discover a prompt": "Faigh amach leid", + "Discover a prompt": "Faigh amach treoir", "Discover a tool": "Faigh amach uirlis", "Discover how to use Open WebUI and seek support from the community.": "Faigh amach conas Open WebUI a úsáid agus lorg tacaíocht ón bpobal.", "Discover wonders": "Faigh amach iontais", "Discover, download, and explore custom functions": "Faigh amach, íoslódáil agus iniúchadh feidhmeanna saincheaptha", - "Discover, download, and explore custom prompts": "Leideanna saincheaptha a fháil amach, a íoslódáil agus a iniúchadh", - "Discover, download, and explore custom tools": "Uirlisí saincheaptha a fháil amach, íoslódáil agus iniúchadh", - "Discover, download, and explore model presets": "Réamhshocruithe samhail a fháil amach, a íoslódáil agus a iniúchadh", + "Discover, download, and explore custom prompts": "Faigh amach, íoslódáil agus iniúch treoracha saincheaptha", + "Discover, download, and explore custom tools": "Faigh amach, íoslódáil agus taiscéal uirlisí saincheaptha", + "Discover, download, and explore model presets": "Faigh amach, íoslódáil agus réamhshocruithe samhail a iniúchadh", "Discussion channel where access is based on groups and permissions": "Cainéal plé ina bhfuil rochtain bunaithe ar ghrúpaí agus ceadanna", "Display": "Taispeáin", "Display chat title in tab": "Taispeáin teideal an chomhrá sa chluaisín", @@ -610,7 +610,7 @@ "Downloading stats...": "Ag íoslódáil staitisticí...", "Draw": "Tarraing", "Drop any files here to upload": "Scaoil aon chomhaid anseo le huaslódáil", - "Drop files here": "", + "Drop files here": "Scaoil comhaid anseo", "Drop files here to upload": "Scaoil comhaid anseo le huaslódáil", "DuckDuckGo": "DuckDuckGo", "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "m.sh. '30s', '10m'. Is iad aonaid ama bailí ná 's', 'm', 'h'.", @@ -663,8 +663,8 @@ "Embedding Concurrent Requests": "Iarratais Chomhuaineacha a Leabú", "Embedding Model": "Samhail Leabháilte", "Embedding Model Engine": "Inneall Samhail Leabaithe", - "Emojis": "", - "Empty message": "", + "Emojis": "Emoji", + "Empty message": "Teachtaireacht folamh", "Enable All": "Cumasaigh Gach Rud", "Enable API Keys": "Cumasaigh Eochracha API", "Enable autocomplete generation for chat messages": "Cumasaigh giniúint uathchríochnaithe le haghaidh teachtaireachtaí comhrá", @@ -755,7 +755,7 @@ "Enter Perplexity Search API URL": "Cuir isteach URL API Cuardaigh na Measctha", "Enter Playwright Timeout": "Iontráil Teorainn Ama na nDrámadóir", "Enter Playwright WebSocket URL": "Cuir isteach URL WebSocket Seinmeora", - "Enter prompt here.": "", + "Enter prompt here.": "Cuir isteach an treoir anseo.", "Enter proxy URL (e.g. https://user:password@host:port)": "Cuir isteach URL seachfhreastalaí (m.sh. https://user:password@host:port)", "Enter reasoning effort": "Cuir isteach iarracht réasúnaíochta", "Enter Score": "Iontráil Scór", @@ -776,11 +776,11 @@ "Enter Sougou Search API sID": "Cuir isteach sID Sougou Search API", "Enter Sougou Search API SK": "Cuir isteach Sougou Search API SK", "Enter stop sequence": "Cuir isteach seicheamh stad", - "Enter system prompt": "Cuir isteach an chóras leid", - "Enter system prompt here": "Cuir leid córais isteach anseo", + "Enter system prompt": "Cuir isteach treoir chórais", + "Enter system prompt here": "Cuir isteach treoir chórais anseo", "Enter Tavily API Key": "Cuir isteach eochair API Tavily", "Enter Tavily Extract Depth": "Cuir isteach Doimhneacht Sliocht Tavily", - "Enter the prompt instructions for this automation...": "", + "Enter the prompt instructions for this automation...": "Cuir isteach treoracha na treorach don uathoibriú seo...", "Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Cuir isteach URL poiblí do WebUI. Bainfear úsáid as an URL seo chun naisc a ghiniúint sna fógraí.", "Enter the URL of the function to import": "Cuir isteach URL na feidhme atá le hallmhairiú", "Enter the URL to import": "Cuir isteach an URL le hallmhairiú", @@ -813,14 +813,14 @@ "Enter Your Username": "Cuir isteach D'Ainm Úsáideora", "Enter your webhook URL": "Cuir isteach URL do webhook", "Entra ID": "Aitheantas Entra", - "Environment Variables": "", - "Ephemeral": "", + "Environment Variables": "Athróga Timpeallachta", + "Ephemeral": "Gearrshaolach", "Error": "Earráid", "ERROR": "EARRÁID", "Error accessing directory": "Earráid ag rochtain eolaire", "Error accessing Google Drive: {{error}}": "Earráid agus tú ag rochtain Google Drive: {{error}}", "Error accessing media devices.": "Earráid ag rochtain gléasanna meán.", - "Error deleting model: {{error}}": "", + "Error deleting model: {{error}}": "Earráid ag scriosadh samhail: {{error}}", "Error starting recording.": "Earráid ag tosú taifeadta.", "Error unloading model: {{error}}": "Earráid ag díluchtú samhail: {{error}}", "Error uploading file: {{error}}": "Earráid agus comhad á uaslódáil: {{error}}", @@ -838,25 +838,25 @@ "Execute code": "Cód a fhorghníomhú", "Execute code for analysis": "Íosluchtaigh cód le haghaidh anailíse", "Executing **{{NAME}}**...": "**{{NAME}}** á rith...", - "Execution Logs": "", + "Execution Logs": "Logaí Forghníomhaithe", "Expand": "Leathnaigh", "Experimental": "Turgnamhach", "Explain": "Mínigh", "Explore the cosmos": "Déan iniúchadh ar an cosmos", - "Explored": "", - "Exploring": "", + "Explored": "Iniúchadh", + "Exploring": "Ag iniúchadh", "Export": "Easpórtáil", "Export All Archived Chats": "Easpórtáil Gach Comhrá Cartlainne", "Export All Chats (All Users)": "Easpórtáil gach comhrá (Gach Úsáideoir)", - "Export as CSV": "", - "Export as JSON": "", + "Export as CSV": "Easpórtáil mar CSV", + "Export as JSON": "Easpórtáil mar JSON", "Export chat (.json)": "Easpórtáil comhrá (.json)", - "Export Chats": "Comhráite Easpórtá", - "Export Config": "Cumraíocht Easpórtála", - "Export Models": "Samhlacha Easpórtála", - "Export Prompts": "Leideanna Easpórtála", + "Export Chats": "Easpórtáil Comhráite", + "Export Config": "Easpórtáil Cumraíocht", + "Export Models": "Easpórtáil Samhlacha", + "Export Prompts": "Easpórtáil Treoracha", "Export to CSV": "Easpórtáil go CSV", - "Export Tools": "Uirlisí Easpórtála", + "Export Tools": "Easpórtáil Uirlisí", "Export Users": "Easpórtáil Úsáideoirí", "External": "Seachtrach", "External Document Loader URL required.": "URL Luchtaitheora Doiciméad Seachtrach ag teastáil.", @@ -868,7 +868,7 @@ "Fade Effect for Streaming Text": "Éifeacht Céimnithe le haghaidh Sruthú Téacs", "Failed to add file.": "Theip ar an gcomhad a chur leis.", "Failed to add members": "Theip ar bhaill a chur leis", - "Failed to archive chat.": "", + "Failed to archive chat.": "Theip ar an gcomhrá a chartlannú.", "Failed to attach file": "Theip ar an gcomhad a cheangal", "Failed to clear status": "Theip ar an stádas a ghlanadh", "Failed to connect to {{URL}} OpenAPI tool server": "Theip ar nascadh le {{URL}} freastalaí uirlisí OpenAPI", @@ -883,11 +883,11 @@ "Failed to generate title": "Theip ar an teideal a ghiniúint", "Failed to import models": "Theip ar samhail a iompórtáil", "Failed to load chat preview": "Theip ar réamhamharc comhrá a lódáil", - "Failed to load DOCX file. Please try downloading it instead.": "", + "Failed to load DOCX file. Please try downloading it instead.": "Theip ar an gcomhad DOCX a luchtú. Déan iarracht é a íoslódáil ina ionad.", "Failed to load Excel/CSV file. Please try downloading it instead.": "Theip ar an gcomhad Excel/CSV a lódáil. Déan iarracht é a íoslódáil ina ionad.", "Failed to load file content.": "Theip ar lódáil ábhar an chomhaid.", "Failed to load Interface settings": "Theip ar shocruithe an Chomhéadain a lódáil", - "Failed to load PPTX file. Please try downloading it instead.": "", + "Failed to load PPTX file. Please try downloading it instead.": "Theip ar an gcomhad PPTX a luchtú. Déan iarracht é a íoslódáil ina ionad.", "Failed to move chat": "Theip ar an gcomhrá a bhogadh", "Failed to process URL: {{url}}": "Theip ar phróiseáil an URL: {{url}}", "Failed to read clipboard contents": "Theip ar ábhar gearrthaisce a lé", @@ -897,7 +897,7 @@ "Failed to save connections": "Theip ar na naisc a shábháil", "Failed to save conversation": "Theip ar an gcomhrá a shábháil", "Failed to save models configuration": "Theip ar chumraíocht na samhlacha a shábháil", - "Failed to save policy: {{error}}": "", + "Failed to save policy: {{error}}": "Theip ar an mbeartas a shábháil: {{error}}", "Failed to save terminal servers": "Theip ar fhreastalaithe críochfoirt a shábháil", "Failed to unshare chat.": "Theip ar an gcomhrá a dhíroinnt.", "Failed to update settings": "Theip ar shocruithe a nuashonrú", @@ -913,7 +913,7 @@ "Feedback History": "Stair Aiseolais", "Feel free to add specific details": "Ná bíodh leisce ort sonraí ar leith a chur leis", "Female": "Baineann", - "Fetch URL Content Length Limit": "", + "Fetch URL Content Length Limit": "Teorainn Fad Ábhar URL a Aisghabháil", "File": "Comhad", "File added successfully.": "D'éirigh leis an gcomhad a chur leis.", "File attached to chat": "Comhad ceangailte leis an gcomhrá", @@ -944,7 +944,7 @@ "Focus Chat Input": "Dírigh ar Ionchur Comhrá", "Folder": "Fillteán", "Folder Background Image": "Íomhá Chúlra Fillteáin", - "Folder created successfully": "", + "Folder created successfully": "Cruthaíodh fillteán go rathúil", "Folder deleted successfully": "Scriosadh an fillteán go rathúil", "Folder Max File Count": "Uasmhéid Líon Comhad Fillteán", "Folder name": "Ainm fillteáin", @@ -956,7 +956,7 @@ "Folders": "Fillteáin", "Follow up": "Leanúint suas", "Follow Up Generation": "Giniúint Leantach", - "Follow Up Generation Prompt": "Leid Ghiniúna Leanúnach", + "Follow Up Generation Prompt": "Treoir Giniúna Leantach", "Follow up: {{question}}": "Leanúint suas: {{question}}", "Follow-Up Auto-Generation": "Uathghiniúint Leantach", "Followed instructions perfectly": "Lean treoracha go foirfe", @@ -968,10 +968,10 @@ "Format Lines": "Formáid Línte", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "Formáidigh na línte san aschur. Is é Bréag an réamhshocrú. Má shocraítear é go Fíor, déanfar na línte a fhormáidiú chun matamaitic agus stíleanna inlíne a bhrath.", "Formatting may be inconsistent from source.": "B’fhéidir nach bhfuil an fhormáidiú comhsheasmhach ón bhfoinse.", - "Forward": "", + "Forward": "Ar Aghaidh", "Forwards system user OAuth access token to authenticate": "Seolann sé comhartha rochtana OAuth úsáideora an chórais ar aghaidh chun fíordheimhniú a dhéanamh", "Forwards system user session credentials to authenticate": "Cuir dintiúir seisiúin úsáideora córais ar aghaidh lena bhfíordheimhniú", - "Fr_day_of_week": "", + "Fr_day_of_week": "Aoine", "Full Context Mode": "Mód Comhthéacs Iomlán", "Function": "Feidhm", "Function Calling": "Glaonna Feidhme", @@ -1046,8 +1046,8 @@ "History": "Stair", "Home": "Baile", "Host": "Óstach", - "Hourly": "", - "Hourly Messages": "Teachtaireachtaí Uaireanta", + "Hourly": "Gach uair an chloig", + "Hourly Messages": "Teachtaireachtaí gach uair an chloig", "How can I help you today?": "Conas is féidir liom cabhrú leat inniu?", "How would you rate this response?": "Cad é mar a mheasfá an freagra seo?", "HTML": "HTML", @@ -1058,7 +1058,7 @@ "ID": "ID", "ID cannot contain \":\" or \"|\" characters": "Ní féidir carachtair \":\" nó \"|\" a bheith san ID", "ID copied to clipboard": "Aitheantas cóipeáilte chuig an ghearrthaisce", - "Idle Timeout": "", + "Idle Timeout": "Am Teorann Díomhaoin", "iframe Sandbox Allow Forms": "iframe Bosca Gainimh Foirmeacha Ceadaithe", "iframe Sandbox Allow Same Origin": "ceadaigh Bosca Gainimh iframe an Bunús Céanna", "Ignite curiosity": "Las fiosracht", @@ -1073,8 +1073,8 @@ "Image Max Compression Size": "Íomhá Méid Comhbhrú Max", "Image Max Compression Size height": "Airde Uasmhéid Comhbhrúite Íomhá", "Image Max Compression Size width": "Leithead Uasmhéid Comhbhrúite Íomhá", - "Image Prompt Generation": "Giniúint Leid Íomhá", - "Image Prompt Generation Prompt": "Leid Giniúint Leide Íomhá", + "Image Prompt Generation": "Giniúint Treoracha Íomhá", + "Image Prompt Generation Prompt": "Treoir Giniúna Treoracha Íomhá", "Image Size": "Méid na hÍomhá", "Images": "Íomhánna", "Import": "Iompórtáil", @@ -1082,7 +1082,7 @@ "Import Config": "Cumraíocht Iompórtála", "Import From Link": "Iompórtáil Ó Nasc", "Import Models": "Iompórtáil Samhlacha", - "Import Prompts": "Leideanna Iompórtála", + "Import Prompts": "Iompórtáil Treoracha", "Import successful": "D'éirigh leis an allmhairiú", "Import Tools": "Uirlisí Iompórtála", "Important Update": "Nuashonrú tábhachtach", @@ -1101,12 +1101,12 @@ "Input Key (e.g. text, unet_name, steps)": "Eochair Ionchuir (m.sh. téacs, unet_name, céimeanna)", "Input Variables": "Athróga Ionchuir", "Insert": "Cuir isteach", - "Insert Follow-Up Prompt to Input": "Cuir isteach leid leantach le hionchur", - "Insert Prompt as Rich Text": "Cuir isteach an leid mar théacs saibhir", - "Insert Suggestion Prompt to Input": "Cuir isteach Moladh Leid chun Ionchur", + "Insert Follow-Up Prompt to Input": "Cuir Treoir Leantach leis an Ionchur", + "Insert Prompt as Rich Text": "Cuir an Treoir isteach mar Théacs Saibhir", + "Insert Suggestion Prompt to Input": "Cuir Treoir Mholta leis an Ionchur", "Install from Github URL": "Suiteáil ó Github URL", "Instant Auto-Send After Voice Transcription": "Seoladh Uathoibríoch Láithreach Tar éis", - "Instructions": "", + "Instructions": "Treoracha", "Integration": "Comhtháthú", "Integrations": "Comhtháthúcháin", "Interface": "Comhéadan", @@ -1136,7 +1136,7 @@ "JWT Expiration": "Éag JWT", "JWT Token": "Comhartha JWT", "Kagi Search API Key": "Eochair API Chuardaigh Kagi", - "Keep Follow-Up Prompts in Chat": "Coinnigh Leideanna Leanúnacha i gComhrá", + "Keep Follow-Up Prompts in Chat": "Coinnigh Treoracha Leantacha sa Chomhrá", "Keep in Sidebar": "Coinnigh sa Bharra Taobh", "Key": "Eochair", "Key is required": "Tá eochair ag teastáil", @@ -1166,7 +1166,7 @@ "Last 90 days": "90 lá seo caite", "Last Active": "Gníomhach Deiridh", "Last Modified": "Athraithe Deiridh", - "Last ran": "", + "Last ran": "Rith dheireanach", "Last reply": "Freagra deiridh", "LDAP": "LDAP", "LDAP server updated": "Nuashonraíodh freastalaí LDAP", @@ -1186,7 +1186,7 @@ "Leave empty to use first admin user": "Fág folamh chun an chéad úsáideoir riarthóra a úsáid", "Leave empty to use the default config, or enter a valid json (see https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)": "Fág folamh chun an chumraíocht réamhshocraithe a úsáid, nó cuir isteach json bailí (féach https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)", "Leave empty to use the default model (voxtral-mini-latest).": "Fág folamh chun an tsamhail réamhshocraithe (voxtral-mini-latest) a úsáid.", - "Leave empty to use the default prompt, or enter a custom prompt": "Fág folamh chun an leid réamhshocraithe a úsáid, nó cuir isteach leid saincheaptha", + "Leave empty to use the default prompt, or enter a custom prompt": "Fág folamh chun an treoir réamhshocraithe a úsáid, nó cuir isteach treoir shaincheaptha", "Leave model field empty to use the default model.": "Fág an réimse samhail folamh chun an tsamhail réamhshocraithe a úsáid.", "Legacy": "Oidhreacht", "lexical": "leicseach", @@ -1231,7 +1231,7 @@ "Max Speakers": "Uasmhéid Cainteoirí", "Max Upload Count": "Líon Uaslódála Max", "Max Upload Size": "Méid Uaslódála Max", - "Maximum characters to return from fetched URLs. Leave empty for no limit.": "", + "Maximum characters to return from fetched URLs. Leave empty for no limit.": "Uasmhéid carachtair le tabhairt ar ais ó URLanna a fuarthas. Fág folamh le haghaidh gan teorainn.", "Maximum number of files allowed per folder.": "Uasmhéid na gcomhad a cheadaítear in aghaidh an fhillteáin.", "Maximum number of files per folder is {{max}}.": "Is é {{max}} an líon uasta comhad in aghaidh an fhillteáin.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Is féidir uasmhéid de 3 samhail a íoslódáil ag an am Bain triail as arís níos déanaí.", @@ -1263,17 +1263,17 @@ "Microsoft OneDrive": "Microsoft OneDrive", "Microsoft OneDrive (personal)": "Microsoft OneDrive (pearsanta)", "Microsoft OneDrive (work/school)": "Microsoft OneDrive (obair/scoil)", - "min": "", + "min": "nóim", "MinerU": "MinerU", "MinerU API Key required for Cloud API mode.": "Eochair API MinerU ag teastáil le haghaidh mód Cloud API.", "Mistral OCR": "OCR Mistral", "Mistral OCR API Key required.": "Mistral OCR API Eochair ag teastáil.", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "Luan", "Model": "Samhail", "Model '{{modelName}}' has been successfully downloaded.": "Rinneadh an tsamhail '{{modelName}}' a íoslódáil go rathúil.", "Model '{{modelTag}}' is already in queue for downloading.": "Tá samhail '{{modelTag}}' sa scuaine cheana féin le híoslódáil.", - "Model {{modelName}} deleted successfully": "", + "Model {{modelName}} deleted successfully": "Scriosadh an tsamhail {{modelName}} go rathúil", "Model {{modelName}} is not vision capable": "Níl samhail {{modelName}} in ann amharc", "Model {{name}} is now {{status}}": "Tá samhail {{name}} {{status}} anois", "Model {{name}} is now hidden": "Tá an tsamhail {{name}} i bhfolach anois", @@ -1281,7 +1281,7 @@ "Model accepts file inputs": "Glacann an tsamhail le hionchuir chomhaid", "Model accepts image inputs": "Glacann an tsamhail le hionchuir íomhá", "Model can execute code and perform calculations": "Is féidir leis an tsamhail cód a fhorghníomhú agus ríomhaireachtaí a dhéanamh", - "Model can generate images based on text prompts": "Is féidir leis an tsamhail íomhánna a ghiniúint bunaithe ar leideanna téacs", + "Model can generate images based on text prompts": "Is féidir leis an tsamhail íomhánna a ghiniúint bunaithe ar threoracha téacs", "Model can search the web for information": "Is féidir leis an tsamhail cuardach a dhéanamh ar an ngréasán le haghaidh faisnéise", "Model Capabilities": "Cumais Samhail", "Model created successfully!": "Cruthaíodh an tsamhail go rathúil!", @@ -1313,22 +1313,22 @@ "Models Sharing": "Roinnt Samhlacha", "Mojeek": "Mojeek", "Mojeek Search API Key": "Eochair API Cuardach Mojeek", - "Monthly": "", + "Monthly": "Míosúil", "More": "Tuilleadh", "More Concise": "Níos Gonta", "More options": "Tuilleadh roghanna", "More Options": "Tuilleadh Roghanna", "Move": "Bog", - "Moved {{name}}": "", + "Moved {{name}}": "Bogadh {{name}}", "My Terminal": "Mo Teirminéal", "Name": "Ainm", "Name and ID are required, please fill them out": "Tá ainm agus aitheantas ag teastáil, líon isteach iad le do thoil", "Name your knowledge base": "Cuir ainm ar do bhunachar eolais", - "Name, prompt, and model are required": "", + "Name, prompt, and model are required": "Tá ainm, treoir agus samhail riachtanach", "Native": "Dúchasach", - "Never": "", + "Never": "Choíche", "New": "Nua", - "New Automation": "", + "New Automation": "Uathoibriú Nua", "New Button": "Cnaipe Nua", "New Chat": "Comhrá Nua", "New File": "Comhad Nua", @@ -1339,7 +1339,7 @@ "New Model": "Samhail Nua", "New Note": "Nóta Nua", "New Password": "Pasfhocal Nua", - "New Prompt": "Leid Nua", + "New Prompt": "Treoir Nua", "New Skill": "Scil Nua", "New Temporary Chat": "Comhrá Sealadach Nua", "New Terminal": "Teirminéal Nua", @@ -1347,11 +1347,11 @@ "New Webhook": "Gréasáin Nua", "new-channel": "nua-chainéil", "Next message": "An chéad teachtaireacht eile", - "Next run": "", + "Next run": "An chéad rith eile", "No access grants. Private to you.": "Gan aon deontais rochtana. Príobháideach duitse.", "No activity data": "Gan aon sonraí gníomhaíochta", "No authentication": "Gan fíordheimhniú", - "No automations found": "", + "No automations found": "Níor aimsíodh aon uathoibrithe", "No chats found": "Ní bhfuarthas aon chomhráite", "No chats found for this user.": "Ní bhfuarthas aon chomhráite don úsáideoir seo.", "No chats found.": "Ní bhfuarthas aon chomhráite.", @@ -1362,22 +1362,22 @@ "No data": "Gan aon sonraí", "No data found": "Níor aimsíodh aon sonraí", "No distance available": "Níl achar ar fáil", - "No execution logs available yet": "", + "No execution logs available yet": "Níl aon logaí forghníomhaithe ar fáil go fóill", "No expiration can pose security risks.": "Ní féidir le haon dáta éaga rioscaí slándála a chruthú.", "No feedback found": "Níor aimsíodh aon aiseolas", "No file selected": "Níl aon chomhad roghnaithe", "No files found": "Níor aimsíodh aon chomhaid", "No files in this knowledge base.": "Níl aon chomhaid sa bhunachar eolais seo.", - "No files yet. Upload files or run Python code to create them.": "", + "No files yet. Upload files or run Python code to create them.": "Gan aon chomhaid fós. Uaslódáil comhaid nó rith cód Python chun iad a chruthú.", "No functions found": "Níor aimsíodh aon fheidhmeanna", "No groups found": "Níor aimsíodh aon ghrúpaí", "No history available": "Níl aon stair ar fáil", "No HTML, CSS, or JavaScript content found.": "Níor aimsíodh aon ábhar HTML, CSS nó JavaScript.", "No inference engine with management support found": "Níor aimsíodh aon inneall tátail le tacaíocht bhainistíochta", - "No kernel": "", + "No kernel": "Gan aon eithne", "No knowledge bases found.": "Níor aimsíodh aon bhunachair eolais.", "No knowledge found": "Níor aimsíodh aon eolas", - "No limit": "", + "No limit": "Gan teorainn", "No memories to clear": "Gan cuimhní cinn a ghlanadh", "No model IDs": "Gan aon aitheantóirí samhail", "No models available": "Níl aon samhlacha ar fáil", @@ -1387,15 +1387,15 @@ "No notes found": "Níor aimsíodh aon nótaí", "No one": "Níl aon duine", "No pinned messages": "Gan aon teachtaireachtaí bioráilte", - "No prompts found": "Níor aimsíodh aon leideanna", + "No prompts found": "Níor aimsíodh aon treoracha", "No results": "Níl aon torthaí le fáil", "No results found": "Níl aon torthaí le fáil", "No search query generated": "Ní ghintear aon cheist cuardaigh", - "No servers detected": "", + "No servers detected": "Níor braitheadh aon fhreastalaithe", "No skills found": "Níor aimsíodh aon scileanna", "No source available": "Níl aon fhoinse ar fáil", "No sources found": "Níor aimsíodh aon fhoinsí", - "No suggestion prompts": "Gan leideanna molta", + "No suggestion prompts": "Gan aon treoracha molta", "No Terminal connection configured.": "Gan nasc teirminéal cumraithe.", "No terminal connections configured.": "Gan aon naisc teirminéal cumraithe.", "No tool server connections configured.": "Níl aon naisc freastalaí uirlisí cumraithe.", @@ -1409,7 +1409,7 @@ "Not factually correct": "Níl sé ceart go fírineach", "Not helpful": "Gan a bheith cabhrach", "Not Registered": "Gan Clárú", - "Not scheduled": "", + "Not scheduled": "Gan sceidealú", "Note": "Nóta", "Note deleted successfully": "Scriosadh an nóta go rathúil", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nóta: Má shocraíonn tú íosscór, ní thabharfaidh an cuardach ach doiciméid a bhfuil scór níos mó ná nó cothrom leis an scór íosta ar ais.", @@ -1422,7 +1422,7 @@ "November": "Samhain", "OAuth": "OAuth", "OAuth 2.1": "OAuth 2.1", - "OAuth 2.1 (Static)": "", + "OAuth 2.1 (Static)": "OAuth 2.1 (Statach)", "OAuth ID": "Aitheantas OAuth", "October": "Deireadh Fómhair", "Off": "As", @@ -1434,7 +1434,7 @@ "Ollama Cloud API Key": "Eochair API Ollama Cloud", "Ollama Version": "Leagan Ollama", "On": "Ar", - "Once": "", + "Once": "Uair amháin", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Gníomhach amháin nuair a bhíonn an socrú \"Greamaigh Téacs Mór mar Chomhad\" casta air.", "Only active when the chat input is in focus and an LLM is generating a response.": "Gníomhach ach amháin nuair a bhíonn an t-ionchur comhrá i bhfócas agus nuair a bhíonn LLM ag giniúint freagra.", @@ -1453,7 +1453,7 @@ "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Tá modh gan tacaíocht á úsáid agat (tosaigh amháin). Freastal ar an WebUI ón gcúltaca le do thoil.", "Open file": "Oscail comhad", "Open in full screen": "Oscail i scáileán iomlán", - "Open in new tab": "", + "Open in new tab": "Oscail i gcluaisín nua", "Open link": "Oscail nasc", "Open modal to configure connection": "Oscail an modal chun an nasc a chumrú", "Open Modal To Manage Floating Quick Actions": "Oscail Modúl Chun Gníomhartha Tapa Snámhacha a Bhainistiú", @@ -1484,7 +1484,7 @@ "or": "nó", "Ordered List": "Liosta Ordaithe", "Other": "Eile", - "out of": "", + "out of": "as", "Output": "Aschur", "OUTPUT": "ASCHUR", "Output format": "Formáid aschuir", @@ -1500,7 +1500,7 @@ "Password": "Pasfhocal", "Passwords do not match.": "Ní hionann na pasfhocail.", "Paste Large Text as File": "Greamaigh Téacs Mór mar Chomhad", - "Paused": "", + "Paused": "Sosaithe", "PDF document (.pdf)": "Doiciméad PDF (.pdf)", "PDF Extract Images (OCR)": "Íomhánna Sliocht PDF (OCR)", "PDF Loader Mode": "Mód Luchtaithe PDF", @@ -1516,13 +1516,12 @@ "Perplexity Model": "Samhail Perplexity", "Perplexity Search API URL": "URL API Cuardaigh Measctha", "Perplexity Search Context Usage": "Úsáid Chomhthéacs Cuardaigh Mearbhall", - "Persistent": "", + "Persistent": "Dianseasmhach", "Personalization": "Pearsantú", "Pin": "Bioráin", - "Pin to Sidebar": "", - "Pinned": "Pinneáilte", - "Pinned Messages": "Teachtaireachtaí Pionáilte", - "Pinned Models": "Samhlacha bioráilte", + "Pinned": "Bioránaithe", + "Pinned Messages": "Teachtaireachtaí Bioránaithe", + "Pinned Models": "Samhlacha Bioránaithe", "Pioneer insights": "Léargais ceannródaí", "Pipe": "Píopa", "Pipeline deleted successfully": "Scriosta píblíne go rathúil", @@ -1537,16 +1536,16 @@ "Playwright Timeout (ms)": "Teorainn Ama drámadóra (ms)", "Playwright WebSocket URL": "URL drámadóir WebSocket", "Please carefully review the following warnings:": "Déan athbhreithniú cúramach ar na rabhaidh seo a leanas le do thoil:", - "Please connect all required integrations before sending a message": "", + "Please connect all required integrations before sending a message": "Ceangail na comhtháthúcháin riachtanacha go léir sula seoltar teachtaireacht", "Please do not close the settings page while loading the model.": "Ná dún leathanach na socruithe agus an tsamhail á luchtú.", "Please enter a message or attach a file.": "Cuir isteach teachtaireacht nó ceangail comhad le do thoil.", - "Please enter a prompt": "Cuir isteach leid", + "Please enter a prompt": "Cuir isteach treoir", "Please enter a valid ID": "Cuir isteach aitheantas bailí le do thoil", "Please enter a valid JSON spec": "Cuir isteach sonraíocht JSON bhailí le do thoil", "Please enter a valid path": "Cuir isteach cosán bailí", "Please enter a valid URL": "Cuir isteach URL bailí", "Please enter a valid URL.": "Cuir isteach URL bailí le do thoil.", - "Please enter Client ID and Client Secret": "", + "Please enter Client ID and Client Secret": "Cuir isteach ID Cliant agus Rún Cliant le do thoil", "Please fill in all fields.": "Líon isteach gach réimse le do thoil.", "Please register the OAuth client": "Cláraigh an cliant OAuth le do thoil", "Please save the connection to persist the OAuth client information and do not change the ID": "Sábháil an nasc le go gcoimeádfar faisnéis an chliaint OAuth agus ná hathraigh an ID.", @@ -1556,9 +1555,9 @@ "Please select a valid JSON file": "Roghnaigh comhad JSON bailí le do thoil", "Please select at least one user for Direct Message channel.": "Roghnaigh úsáideoir amháin ar a laghad don chainéal Teachtaireachtaí Díreacha.", "Please wait until all files are uploaded.": "Fan go dtí go mbeidh na comhaid go léir uaslódáilte.", - "Policy ID": "", + "Policy ID": "Aitheantas Polasaí", "Port": "Port", - "Ports": "", + "Ports": "Poirt", "Positive attitude": "Dearcadh dearfach", "Prefer not to say": "Is fearr liom gan a rá", "Prefix ID": "Aitheantas Réimír", @@ -1572,29 +1571,29 @@ "Private conversation between selected users": "Comhrá príobháideach idir úsáideoirí roghnaithe", "Production version updated": "Leagan táirgeachta nuashonraithe", "Profile": "Próifíl", - "Prompt": "Leid", - "Prompt Autocompletion": "Uathchríochnú Pras", - "Prompt Content": "Ábhar Leid", - "Prompt created successfully": "Leid cruthaithe go rathúil", - "Prompt Name": "Ainm an Phraghas", - "Prompt Suggestions": "Moltaí Treoir", - "Prompt updated successfully": "D'éirigh leis an leid a nuashonrú", - "Prompts": "Leabhair", - "Prompts Access": "Rochtain ar Chuirí", - "Prompts Public Sharing": "Spreagann Roinnt Phoiblí", - "Prompts Sharing": "Comhroinnt Leideanna", + "Prompt": "Treoir", + "Prompt Autocompletion": "Uathchríochnú Treoracha", + "Prompt Content": "Ábhar na Treorach", + "Prompt created successfully": "Cruthaíodh an treoir go rathúil", + "Prompt Name": "Ainm na Treorach", + "Prompt Suggestions": "Moltaí Treoracha", + "Prompt updated successfully": "Nuashonraíodh an treoir go rathúil", + "Prompts": "Treoracha", + "Prompts Access": "Rochtain ar Threoracha", + "Prompts Public Sharing": "Comhroinnt Phoiblí Treoracha", + "Prompts Sharing": "Comhroinnt Treoracha", "Provider Type": "Cineál Soláthraí", "Public": "Poiblí", "Pull \"{{searchValue}}\" from Ollama.com": "Tarraing \"{{searchValue}}\" ó Ollama.com", "Pull a model from Ollama.com": "Tarraing samhail ó Ollama.com", "Pull Model": "Samhail Tarraingthe", - "Pyodide file browser": "", - "Query Generation Prompt": "Cuirí Ginearáil Ceisteanna", + "Pyodide file browser": "Brabhsálaí comhad Pyodide", + "Query Generation Prompt": "Treoir Giniúna Ceisteanna", "Querying": "Ag fiosrú", "Quick Actions": "Gníomhartha Tapa", "RAG Template": "Teimpléad RAG", - "Ran {{COUNT}} analyses": "", - "Ran {{COUNT}} analysis": "", + "Ran {{COUNT}} analyses": "Rinne {{COUNT}} anailísí", + "Ran {{COUNT}} analysis": "Rinneadh anailís ar {{COUNT}}", "Rate {{rating}} out of 10": "Rátáil {{rating}} as 10", "Rating": "Rátáil", "Re-rank models by topic similarity": "Athrangú samhlacha de réir cosúlachta topaice", @@ -1606,7 +1605,7 @@ "Reason": "Cúis", "Reasoning Effort": "Iarracht Réasúnúcháin", "Reasoning Tags": "Clibeanna Réasúnaíochta", - "Recently Used": "", + "Recently Used": "Úsáidte le Déanaí", "Record": "Taifead", "Record voice": "Taifead guth", "Redirecting you to Open WebUI Community": "Tú a atreorú chuig OpenWebUI Community", @@ -1639,10 +1638,10 @@ "Remove image": "Bain íomhá", "Remove Model": "Bain an tSamhail", "Rename": "Athainmnigh", - "Renamed to {{name}}": "", + "Renamed to {{name}}": "Athainmnithe go {{name}}", "Render Markdown in Previews": "Rindreáil Markdown i Réamhamhairc", "Reorder Models": "Athordú na Samhlacha", - "Repeats": "", + "Repeats": "Athdhéantar", "Reply": "Freagra", "Reply in Thread": "Freagra i Snáithe", "Reply to thread...": "Freagra ar an snáithe...", @@ -1661,9 +1660,8 @@ "Response splitting": "Scoilt freagartha", "Response Watermark": "Comhartha Uisce Freagartha", "Responses": "Freagraí", - "Restart": "", "Result": "Toradh", - "RESULT": "Toradh", + "RESULT": "TORADH", "Retrieval": "Aisghabháil", "Retrieval Query Generation": "Aisghabháil Giniúint Ceist", "Retrieved {{count}} sources": "Aisghafa {{count}} foinsí", @@ -1674,13 +1672,13 @@ "Role": "Ról", "RTL": "RTL", "Run": "Rith", - "Run All": "", - "Run now": "", - "Run Now": "", + "Run All": "Rith Gach Rud", + "Run now": "Rith anois", + "Run Now": "Rith Anois", "Running": "Ag rith", "Running...": "Ag rith...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Ritheann sé tascanna leabaithe ag an am céanna chun luas a chur leis an bpróiseáil. Múch é má bhíonn teorainneacha ráta ina bhfadhb.", - "Sa_day_of_week": "", + "Sa_day_of_week": "Satharn", "Save": "Sábháil", "Save & Create": "Sábháil & Cruthaigh", "Save & Update": "Sábháil & Nuashonraigh", @@ -1688,15 +1686,15 @@ "Save Chat": "Sábháil Comhrá", "Saved": "Shábháil", "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ní thacaítear le logaí comhrá a shábháil go díreach chuig stóráil do bhrabhsálaí Tóg nóiméad chun do logaí comhrá a íoslódáil agus a scriosadh trí chliceáil an cnaipe thíos. Ná bíodh imní ort, is féidir leat do logaí comhrá a athiompórtáil go héasca chuig an gcúltaca trí", - "Schedule": "", - "Scheduled time must be in the future": "", + "Schedule": "Sceideal", + "Scheduled time must be in the future": "Ní mór don am sceidealaithe a bheith sa todhchaí", "Scroll On Branch Change": "Scrollaigh ar Athrú Brainse", "Search": "Cuardaigh", "Search a model": "Cuardaigh samhail", "Search all emojis": "Cuardaigh gach emoji", "Search and manage user memories": "Cuardaigh agus bainistigh cuimhní úsáideora", "Search and view user chat history": "Cuardaigh agus féach ar stair comhrá úsáideora", - "Search Automations": "", + "Search Automations": "Uathoibriúcháin Cuardaigh", "Search Base": "Bonn Cuardaigh", "Search channels and channel messages": "Cuardaigh bealaí agus teachtaireachtaí bealaí", "Search Chats": "Cuardaigh Comhráite", @@ -1712,11 +1710,11 @@ "Search Groups": "Cuardaigh Grúpaí", "Search In Models": "Cuardaigh i Samhlacha", "Search Knowledge": "Cuardaigh Eolais", - "Search Memories": "", + "Search Memories": "Cuardaigh Cuimhní", "Search Models": "Cuardaigh Samhlacha", "Search Notes": "Cuardaigh Nótaí", "Search options": "Roghanna cuardaigh", - "Search Prompts": "Leideanna Cuardaigh", + "Search Prompts": "Treoracha Cuardaigh", "Search Result Count": "Líon Torthaí Cuardaigh", "Search Skills": "Scileanna Cuardaigh", "Search the internet": "Cuardaigh an tIdirlíon", @@ -1754,7 +1752,7 @@ "Select a theme": "Roghnaigh téama", "Select a tool": "Roghnaigh uirlis", "Select a voice": "Roghnaigh guth", - "Select All": "", + "Select All": "Roghnaigh Uile", "Select an auth method": "Roghnaigh modh an údair", "Select an embedding model engine": "Roghnaigh inneall samhail leabaithe", "Select an engine": "Roghnaigh inneall", @@ -1766,7 +1764,7 @@ "Select how to split message text for TTS requests": "Roghnaigh conas téacs teachtaireachta a roinnt le haghaidh iarratais TTS", "Select Knowledge": "Roghnaigh Eolais", "Select Method": "Roghnaigh Modh", - "Select model": "", + "Select model": "Roghnaigh samhail", "Select only one model to call": "Roghnaigh samhail amháin le glaoch", "Select view": "Roghnaigh radharc", "Selected model: {{modelName}}": "Samhail roghnaithe: {{modelName}}", @@ -1784,7 +1782,7 @@ "Serper API Key": "Serper API Eochair", "Serply API Key": "Eochair API Serply", "Serpstack API Key": "Eochair API Serpstack", - "Server connection failed": "", + "Server connection failed": "Theip ar cheangal leis an bhfreastalaí", "Server connection verified": "Ceangal freastalaí fíoraithe", "Session": "Seisiún", "Set as default": "Socraigh mar réamhshocraithe", @@ -1802,7 +1800,7 @@ "Sets a flat bias against tokens that have appeared at least once. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled.": "Socraíonn sé claonadh cothrom i gcoinne comharthaí a tháinig chun solais uair amháin ar a laghad. Cuirfidh luach níos airde (m.sh., 1.5) pionós níos láidre ar athrá, agus beidh luach níos ísle (m.sh., 0.9) níos boige. Ag 0, tá sé díchumasaithe.", "Sets a scaling bias against tokens to penalize repetitions, based on how many times they have appeared. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled.": "Socraíonn sé laofacht scálaithe i gcoinne comharthaí chun pionós a ghearradh ar athrá, bunaithe ar cé mhéad uair a tháinig siad chun solais. Cuirfidh luach níos airde (m.sh., 1.5) pionós níos láidre ar athrá, agus beidh luach níos ísle (m.sh., 0.9) níos boige. Ag 0, tá sé díchumasaithe.", "Sets how far back for the model to look back to prevent repetition.": "Socraíonn sé cé chomh fada siar is atá an tsamhail le breathnú siar chun athrá a chosc.", - "Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt.": "Socraíonn sé an síol uimhir randamach a úsáid le haghaidh giniúna. Má shocraítear é seo ar uimhir shainiúil, ginfidh an tsamhail an téacs céanna don leid céanna.", + "Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt.": "Socraíonn sé an síol uimhir randamach a úsáid le haghaidh giniúna. Má shocraítear é seo ar uimhir shainiúil, ginfidh an tsamhail an téacs céanna don treoir céanna.", "Sets the size of the context window used to generate the next token.": "Socraíonn sé méid na fuinneoige comhthéacs a úsáidtear chun an chéad chomhartha eile a ghiniúint.", "Sets the stop sequences to use. When this pattern is encountered, the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.": "Socraíonn sé na stadanna le húsáid. Nuair a thagtar ar an bpatrún seo, stopfaidh an LLM ag giniúint téacs agus ag filleadh. Is féidir patrúin stad iolracha a shocrú trí pharaiméadair stadanna iolracha a shonrú i gcomhad samhail.", "Setting": "Socrú", @@ -1875,8 +1873,8 @@ "Start a new conversation": "Tosaigh comhrá nua", "Start of the channel": "Tús an chainéil", "Start Tag": "Clib Tosaigh", - "Starting kernel...": "", - "State": "", + "Starting kernel...": "Ag tosú an eithne...", + "State": "Stát", "Status": "Stádas", "Status cleared successfully": "Glanadh an stádais go rathúil", "Status updated successfully": "Nuashonraíodh an stádas go rathúil", @@ -1887,7 +1885,7 @@ "Stop Download": "Stop an Íoslódáil", "Stop Generating": "Stop a Ghiniúint", "Stop Sequence": "Stop Seicheamh", - "Storage": "", + "Storage": "Stóráil", "Stream Chat Response": "Freagra Comhrá Sruth", "Stream Delta Chunk Size": "Sruth Méid Leadhb Delta", "Streamable HTTP": "HTTP sruthaithe", @@ -1897,7 +1895,7 @@ "STT Model": "Samhail STT", "STT Settings": "Socruithe STT", "Stylized PDF Export": "Easpórtáil PDF Stílithe", - "Su_day_of_week": "", + "Su_day_of_week": "Domhnaigh", "Submit question": "Cuir ceist isteach", "Submit suggestion": "Cuir moladh isteach", "Subtitle": "Fotheideal", @@ -1919,19 +1917,19 @@ "Syncs only chats with updates after your last sync timestamp. Disable to re-sync all chats.": "Ní shioncrónaíonn sé ach comhráite le nuashonruithe tar éis do stampa ama sioncrónaithe deireanach. Díchumasaigh chun gach comhrá a athshioncrónú.", "System": "Córas", "System Instructions": "Treoracha Córas", - "System Prompt": "Córas Leid", + "System Prompt": "Treoir Chóras", "Tag": "Clib", "Tags": "Clibeanna", "Tags Generation": "Giniúint Clibeanna", - "Tags Generation Prompt": "Clibeanna Giniúint Leid", + "Tags Generation Prompt": "Treoir Giniúna Clibeanna", "Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting.": "Úsáidtear sampláil saor ó eireabaill chun tionchar na n-chomharthaí ón aschur nach bhfuil chomh dóchúil céanna a laghdú. Laghdóidh luach níos airde (m.sh., 2.0) an tionchar níos mó, agus díchumasaíonn luach 1.0 an socrú seo. (réamhshocraithe: 1)", "Talk to Model": "Labhair leis an tSamhail", "Tap to interrupt": "Tapáil chun cur isteach", "Task List": "Liosta Tascanna", - "Task Management": "", - "Task Model": "Samhail Thasc", + "Task Management": "Bainistíocht Tascanna", + "Task Model": "Samhail Tasc", "Tasks": "Tascanna", - "tasks completed": "", + "tasks completed": "tascanna críochnaithe", "Tavily API Key": "Eochair API Tavily", "Tavily Extract Depth": "Doimhneacht Sliocht Tavily", "Tell us more:": "Inis dúinn níos mó:", @@ -1943,7 +1941,7 @@ "Text Splitter": "Scoilteoir Téacs", "Text-to-Speech": "Téacs-go-Caint", "Text-to-Speech Engine": "Inneall téacs-go-labhra", - "Th_day_of_week": "", + "Th_day_of_week": "Déardaoin", "Thanks for your feedback!": "Go raibh maith agat as do chuid aiseolas!", "The Application Account DN you bind with for search": "An Cuntas Feidhmchláir DN a nascann tú leis le haghaidh cuardaigh", "The base to search for users": "An bonn chun cuardach a dhéanamh ar úsáideoirí", @@ -1990,7 +1988,7 @@ "This will delete all models including custom models and cannot be undone.": "Scriosfaidh sé seo gach samhail, lena n-áirítear samhlacha saincheaptha, agus ní féidir é a chealú.", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Déanfaidh sé seo an bonn eolais a athshocrú agus gach comhad a shioncronú. Ar mhaith leat leanúint ar aghaidh?", "Thorough explanation": "Míniú críochnúil", - "Thought": "", + "Thought": "Smaoineamh", "Thought for {{DURATION}}": "Smaoineamh ar {{DURATION}}", "Thought for {{DURATION}} seconds": "Smaoineamh ar feadh {{DURATION}} soicind", "Thought for less than a second": "Smaoinigh mé ar feadh níos lú ná soicind", @@ -1999,14 +1997,14 @@ "Tika": "Tika", "Tika Server URL required.": "Teastaíonn URL Freastalaí Tika.", "Tiktoken": "Tiktoken", - "Time": "", + "Time": "Am", "Time & Calculation": "Am & Ríomh", "Timeout": "Am istigh", "Title": "Teideal", "Title Auto-Generation": "Teideal Auto-Generation", "Title cannot be an empty string.": "Ní féidir leis an teideal a bheith ina teaghrán folamh.", "Title Generation": "Giniúint Teidil", - "Title Generation Prompt": "Leid Giniúint Teideal", + "Title Generation Prompt": "Treoir Giniúna Teidil", "TLS": "TLS", "To access the available model names for downloading,": "Chun rochtain a fháil ar ainmneacha na samhlacha atá ar fáil lena n-íoslódáil,", "To access the GGUF models available for downloading,": "Chun rochtain a fháil ar na samhlacha GGUF atá ar fáil lena n-íoslódáil,", @@ -2017,11 +2015,11 @@ "To select toolkits here, add them to the \"Tools\" workspace first.": "Chun trealamh uirlisí a roghnú anseo, cuir iad leis an spás oibre \"Uirlisí\" ar dtús.", "Toast notifications for new updates": "Fógraí tósta le haghaidh nuashonruithe nua", "Today": "Inniu", - "Today at": "", + "Today at": "Inniu ag", "Today at {{LOCALIZED_TIME}}": "Inniu ag {{LOCALIZED_TIME}}", "Toggle {{COUNT}} sources": "Athraigh {{COUNT}} foinsí", "Toggle 1 source": "Athraigh foinse amháin", - "Toggle details": "", + "Toggle details": "Athraigh sonraí", "Toggle Dictation": "Athraigh Deachtú", "Toggle Sidebar": "Barra Taobh a Athraigh", "Toggle status history": "Athraigh stair stádais", @@ -2042,7 +2040,7 @@ "Tools": "Uirlisí", "Tools Access": "Rochtain Uirlisí", "Tools are a function calling system with arbitrary code execution": "Is córas glaonna feidhme iad uirlisí le forghníomhú cód treallach", - "Tools Function Calling Prompt": "Leid Glaonna Feidhm Uirlisí", + "Tools Function Calling Prompt": "Treoir Ghlao Feidhme Uirlisí", "Tools have a function calling system that allows arbitrary code execution.": "Tá córas glaonna feidhme ag uirlisí a cheadaíonn forghníomhú cód treallach.", "Tools Public Sharing": "Uirlisí Roinnte Poiblí", "Tools Sharing": "Comhroinnt Uirlisí", @@ -2057,7 +2055,7 @@ "TTS Model": "Samhail TTS", "TTS Settings": "Socruithe TTS", "TTS Voice": "Guth TTS", - "Tu_day_of_week": "", + "Tu_day_of_week": "Máirt", "Type": "Cineál", "Type here...": "Clóscríobh anseo...", "Type Hugging Face Resolve (Download) URL": "Cineál Hugging Face Resolve (Íoslódáil) URL", @@ -2106,7 +2104,7 @@ "URL Mode": "Mód URL", "Usage": "Úsáid", "Use": "Úsáid", - "Use '#' in the prompt input to load and include your knowledge.": "Úsáid '#' san ionchur leid chun do chuid eolais a lódáil agus a chur san áireamh.", + "Use '#' in the prompt input to load and include your knowledge.": "Úsáid '#' san ionchur treoir chun do chuid eolais a lódáil agus a chur san áireamh.", "Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "Bain úsáid as an gcríochphointe /v1/chat/completions in ionad /v1/audio/transcriptions le haghaidh cruinneas níos fearr b’fhéidir.", "Use Chat Completions API": "Úsáid API Comhlánuithe Comhrá", "Use groups to organize your users and assign permissions.": "Bain úsáid as grúpaí chun d’úsáideoirí a eagrú agus ceadanna a shannadh.", @@ -2152,15 +2150,15 @@ "Voice": "Guth", "Voice Input": "Ionchur Gutha", "Voice mode": "Mod Gutha", - "Voice Mode Custom Prompt": "Leid Saincheaptha Mód Gutha", - "Voice Mode Prompt": "Leid Mód Gutha", + "Voice Mode Custom Prompt": "Treoir Saincheaptha Mód Gutha", + "Voice Mode Prompt": "Treoir Mód Gutha", "Waiting for upload...": "Ag fanacht le huaslódáil...", "Warning": "Rabhadh", "Warning:": "Rabhadh:", - "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", + "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Rabhadh: Trí seo a chumasú, beidh úsáideoirí in ann leideanna sceidealaithe a rith go huathoibríoch.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Rabhadh: Cuirfidh sé seo ar chumas úsáideoirí cód treallach a uaslódáil ar an bhfreastalaí.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Rabhadh: Trí fhorghníomhú Jupyter is féidir cód a fhorghníomhú go treallach, rud a chruthaíonn mór-rioscaí slándála - bí fíorchúramach.", - "We_day_of_week": "", + "We_day_of_week": "Céadaoin", "Web": "Gréasán", "Web API": "API Gréasáin", "Web Loader Engine": "Inneall Luchtaithe Gréasáin", @@ -2177,7 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "Déanfaidh WebUI iarratais ar \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "Déanfaidh WebUI iarratais ar \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "Déanfaidh WebUI iarratais ar \"{{url}}/chat/completions\"", - "Weekly": "", + "Weekly": "Seachtainiúil", "What are you trying to achieve?": "Cad atá tú ag iarraidh a bhaint amach?", "What are you working on?": "Cad air a bhfuil tú ag obair?", "What is NOT shared:": "Cad NACH roinntear", @@ -2194,14 +2192,14 @@ "Width": "Leithead", "Wikipedia": "Vicipéid", "Won": "Bhuaigh", - "Working Directory": "", + "Working Directory": "Eolaire Oibre", "Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text.": "Oibríonn sé le barr-k. Beidh téacs níos éagsúla mar thoradh ar luach níos airde (m.sh., 0.95), agus ginfidh luach níos ísle (m.sh., 0.5) téacs níos dírithe agus níos coimeádaí.", "Workspace": "Spás oibre", "Workspace Permissions": "Ceadanna Spás Oibre", "Write": "Scríobh", "Write a summary in 50 words that summarizes {{topic}}.": "Scríobh achoimre i 50 focal a dhéanann achoimre ar [ábhar nó eochairfhocal].", "Write something...": "Scríobh rud...", - "Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.": "Scríobh inneachar system prompt do mhúnla anseo\nm.sh.: Is é Mario ó Super Mario Bros tú agus tá tú ag gníomhú mar chúntóir.", + "Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.": "Scríobh inneachar threoir chórais do mhúnla anseo\nm.sh.: Is é Mario ó Super Mario Bros tú, ag gníomhú mar chúntóir.", "Yacy Instance URL": "URL Cás Yacy", "Yacy Password": "Pasfhocal Yacy", "Yacy Username": "Ainm úsáideora Yacy", @@ -2218,7 +2216,7 @@ "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Is féidir leat do chuid idirghníomhaíochtaí le LLManna a phearsantú ach cuimhní cinn a chur leis tríd an gcnaipe 'Bainistigh' thíos, rud a fhágann go mbeidh siad níos cabhrach agus níos oiriúnaí duit.", "You cannot upload an empty file.": "Ní féidir leat comhad folamh a uaslódáil.", "You do not have permission to edit this model": "Níl cead agat an tsamhail seo a chur in eagar", - "You do not have permission to edit this prompt.": "Níl cead agat an leid seo a chur in eagar.", + "You do not have permission to edit this prompt.": "Níl cead agat an treoir seo a chur in eagar.", "You do not have permission to edit this skill.": "Níl cead agat an scil seo a chur in eagar.", "You do not have permission to edit this tool": "Níl cead agat an uirlis seo a chur in eagar", "You do not have permission to make this public": "Níl cead agat é seo a chur ar fáil don phobal", @@ -2235,8 +2233,8 @@ "You're now logged in.": "Tá tú logáilte isteach anois.", "Your Account": "Do Chuntas", "Your account status is currently pending activation.": "Tá stádas do chuntais ar feitheamh faoi ghníomhachtú.", - "Your browser does not support the audio tag.": "", - "Your browser does not support the video tag.": "", + "Your browser does not support the audio tag.": "Ní thacaíonn do bhrabhsálaí leis an gclib fuaime.", + "Your browser does not support the video tag.": "Ní thacaíonn do bhrabhsálaí leis an gclib físeáin.", "Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Rachaidh do ranníocaíocht iomlán go díreach chuig an bhforbróir breiseán; Ní ghlacann Open WebUI aon chéatadán. Mar sin féin, d'fhéadfadh a tháillí féin a bheith ag an ardán maoinithe roghnaithe.", "Your message text or inputs": "Téacs nó ionchur do theachtaireachta", "Your usage stats have been successfully synced.": "Tá do staitisticí úsáide sioncronaithe go rathúil.", From 3f40d9da705a49d5f4def848b4447e873e0c0c98 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Thu, 16 Apr 2026 01:19:08 +0800 Subject: [PATCH 003/119] i18n: improve Chinese translation (#23753) --- src/lib/i18n/locales/zh-CN/translation.json | 28 ++++++++++----------- src/lib/i18n/locales/zh-TW/translation.json | 28 ++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index a089bb8e01..eca4f34e43 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -453,7 +453,7 @@ "Create a new note": "新建笔记", "Create Account": "创建账号", "Create Admin Account": "创建管理员账号", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "创建和管理计划自动化任务", "Create Channel": "创建频道", "Create Folder": "创建分组", "Create Image": "图片生成", @@ -479,7 +479,7 @@ "Custom Gender": "自定义性别", "Custom Parameter Name": "自定义参数名称", "Custom Parameter Value": "自定义参数值", - "Daily": "", + "Daily": "每日", "Daily Messages": "每日消息数", "Danger Zone": "危险区域", "Dark": "暗色", @@ -970,7 +970,7 @@ "Forward": "前进", "Forwards system user OAuth access token to authenticate": "转发用户的 OAuth 访问令牌(Access Token)以进行身份验证", "Forwards system user session credentials to authenticate": "转发用户的会话凭证(Session Credentials)以进行身份验证", - "Fr_day_of_week": "", + "Fr_day_of_week": "周五", "Full Context Mode": "完整上下文模式", "Function": "函数", "Function Calling": "函数调用 (Function Calling)", @@ -1045,7 +1045,7 @@ "History": "历史记录", "Home": "主页", "Host": "主机", - "Hourly": "", + "Hourly": "每小时", "Hourly Messages": "每小时消息数", "How can I help you today?": "有什么我能帮您的吗?", "How would you rate this response?": "您如何评价这个回答?", @@ -1268,7 +1268,7 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "需要 Mistral OCR 接口密钥", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "周一", "Model": "模型", "Model '{{modelName}}' has been successfully downloaded.": "模型“{{modelName}}”已成功下载", "Model '{{modelTag}}' is already in queue for downloading.": "模型“{{modelTag}}”已在下载队列中", @@ -1312,7 +1312,7 @@ "Models Sharing": "分享模型", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search 接口密钥", - "Monthly": "", + "Monthly": "每月", "More": "更多", "More Concise": "精炼表达", "More options": "更多选项", @@ -1433,7 +1433,7 @@ "Ollama Cloud API Key": "Ollama Cloud 接口密钥", "Ollama Version": "Ollama 版本", "On": "开启", - "Once": "", + "Once": "单次", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "仅在启用“粘贴超长文本为文件”选项时有效。", "Only active when the chat input is in focus and an LLM is generating a response.": "仅在聚焦对话框且大语言模型正在生成回答时有效。", @@ -1518,7 +1518,7 @@ "Persistent": "持久化", "Personalization": "个性化", "Pin": "置顶", - "Pin to Sidebar": "", + "Pin to Sidebar": "固定到侧边栏", "Pinned": "已置顶", "Pinned Messages": "置顶消息", "Pinned Models": "固定在侧边栏的模型", @@ -1678,7 +1678,7 @@ "Running": "运行中", "Running...": "运行中...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "并行运行嵌入任务以加快处理速度。如果遇到限速问题,请关闭此选项。", - "Sa_day_of_week": "", + "Sa_day_of_week": "周六", "Save": "保存", "Save & Create": "保存并创建", "Save & Update": "保存并更新", @@ -1895,7 +1895,7 @@ "STT Model": "语音转文本模型", "STT Settings": "语音转文本设置", "Stylized PDF Export": "美化 PDF 导出", - "Su_day_of_week": "", + "Su_day_of_week": "周日", "Submit question": "提交问题", "Submit suggestion": "提交建议", "Subtitle": "副标题", @@ -1941,7 +1941,7 @@ "Text Splitter": "文本切分器", "Text-to-Speech": "文本转语音", "Text-to-Speech Engine": "文本转语音引擎", - "Th_day_of_week": "", + "Th_day_of_week": "周四", "Thanks for your feedback!": "感谢您的反馈!", "The Application Account DN you bind with for search": "您所绑定用于搜索的 Application Account DN", "The base to search for users": "搜索用户的 Base", @@ -2055,7 +2055,7 @@ "TTS Model": "文本转语音模型", "TTS Settings": "文本转语音设置", "TTS Voice": "文本转语音音色", - "Tu_day_of_week": "", + "Tu_day_of_week": "周二", "Type": "类型", "Type here...": "请输入内容...", "Type Hugging Face Resolve (Download) URL": "输入 Hugging Face 模型解析(下载)地址", @@ -2158,7 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "警告:启用后将允许用户自动执行定时提示词任务。", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:启用此功能将允许用户在服务器上上传任意代码", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告:启用 Jupyter 执行将允许运行任意代码,存在严重安全风险——务必谨慎操作", - "We_day_of_week": "", + "We_day_of_week": "周三", "Web": "网页", "Web API": "网页 API", "Web Loader Engine": "网页加载引擎", @@ -2175,7 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI 将向 \"{{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\" 发出请求", - "Weekly": "", + "Weekly": "每周", "What are you trying to achieve?": "您想要达到什么目标?", "What are you working on?": "您在忙于什么?", "What is NOT shared:": "不共享的内容:", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index f90782c3f6..70f075130a 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -453,7 +453,7 @@ "Create a new note": "新建筆記", "Create Account": "建立帳號", "Create Admin Account": "建立管理員帳號", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "建立和管理計畫自動化任務", "Create Channel": "建立頻道", "Create Folder": "建立分組", "Create Image": "產生圖片", @@ -479,7 +479,7 @@ "Custom Gender": "自訂性別", "Custom Parameter Name": "自訂參數名稱", "Custom Parameter Value": "自訂參數值", - "Daily": "", + "Daily": "每日", "Daily Messages": "每日訊息數", "Danger Zone": "危險區域", "Dark": "深色", @@ -970,7 +970,7 @@ "Forward": "前進", "Forwards system user OAuth access token to authenticate": "轉發使用者 OAuth 存取權杖(Access Token)以進行驗證", "Forwards system user session credentials to authenticate": "轉發使用者工作階段憑證(Session Credentials)以進行驗證", - "Fr_day_of_week": "", + "Fr_day_of_week": "週五", "Full Context Mode": "完整上下文模式", "Function": "函式", "Function Calling": "函式呼叫", @@ -1045,7 +1045,7 @@ "History": "歷史紀錄", "Home": "首頁", "Host": "主機", - "Hourly": "", + "Hourly": "每小時", "Hourly Messages": "每小時訊息數", "How can I help you today?": "今天我能為您做些什麼?", "How would you rate this response?": "您如何評價此回應?", @@ -1268,7 +1268,7 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "需要提供 Mistral OCR API 金鑰。", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "週一", "Model": "模型", "Model '{{modelName}}' has been successfully downloaded.": "模型「{{modelName}}」已成功下載。", "Model '{{modelTag}}' is already in queue for downloading.": "模型「{{modelTag}}」已在下載佇列中。", @@ -1312,7 +1312,7 @@ "Models Sharing": "分享模型", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek 搜尋 API 金鑰", - "Monthly": "", + "Monthly": "每月", "More": "更多", "More Concise": "精煉表達", "More options": "更多選項", @@ -1433,7 +1433,7 @@ "Ollama Cloud API Key": "Ollama Cloud API 金鑰", "Ollama Version": "Ollama 版本", "On": "開啟", - "Once": "", + "Once": "一次", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "僅在啟用「將大型文字作為檔案貼上」設定時有效。", "Only active when the chat input is in focus and an LLM is generating a response.": "僅在對話輸入框聚焦且大型語言模型正在產生回應時有效。", @@ -1518,7 +1518,7 @@ "Persistent": "持久性", "Personalization": "個人化", "Pin": "釘選", - "Pin to Sidebar": "", + "Pin to Sidebar": "固定到側邊欄", "Pinned": "已釘選", "Pinned Messages": "置頂訊息", "Pinned Models": "固定於側邊欄的模型", @@ -1678,7 +1678,7 @@ "Running": "正在執行", "Running...": "正在執行...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "同時執行嵌入任務以加快處理速度。如果遇到速率限制問題,請關閉此功能。", - "Sa_day_of_week": "", + "Sa_day_of_week": "週六", "Save": "儲存", "Save & Create": "儲存並建立", "Save & Update": "儲存並更新", @@ -1895,7 +1895,7 @@ "STT Model": "語音轉文字 (STT) 模型", "STT Settings": "語音轉文字 (STT) 設定", "Stylized PDF Export": "風格化 PDF 匯出", - "Su_day_of_week": "", + "Su_day_of_week": "週日", "Submit question": "提交問題", "Submit suggestion": "提交建議", "Subtitle": "副標題", @@ -1941,7 +1941,7 @@ "Text Splitter": "文字分割器", "Text-to-Speech": "文字轉語音", "Text-to-Speech Engine": "文字轉語音引擎", - "Th_day_of_week": "", + "Th_day_of_week": "週四", "Thanks for your feedback!": "感謝您的回饋!", "The Application Account DN you bind with for search": "您綁定用於搜尋的應用程式帳號 DN", "The base to search for users": "搜尋使用者的基礎", @@ -2055,7 +2055,7 @@ "TTS Model": "文字轉語音 (TTS) 模型", "TTS Settings": "文字轉語音 (TTS) 設定", "TTS Voice": "文字轉語音 (TTS) 聲音", - "Tu_day_of_week": "", + "Tu_day_of_week": "週二", "Type": "類型", "Type here...": "在此輸入...", "Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 的解析(下載)URL", @@ -2158,7 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "警告:啟用後將允許使用者自動執行排程提示詞。", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:啟用此功能將允許使用者在伺服器上上傳任意程式碼。", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告:Jupyter 執行允許任意程式碼執行,構成嚴重安全風險 —— 請務必極度謹慎。", - "We_day_of_week": "", + "We_day_of_week": "週三", "Web": "網頁", "Web API": "網頁 API", "Web Loader Engine": "網頁載入引擎", @@ -2175,7 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI 將向 \"{{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\" 傳送請求", - "Weekly": "", + "Weekly": "每週", "What are you trying to achieve?": "您正在試圖完成什麼?", "What are you working on?": "您現在的工作是什麼?", "What is NOT shared:": "不會分享的內容:", From ee1bc5f5dbaf2397c03227878974dc43524408a3 Mon Sep 17 00:00:00 2001 From: ecogetaway Date: Wed, 15 Apr 2026 22:49:32 +0530 Subject: [PATCH 004/119] fix(i18n): correct erroneous Hindi translations in hi-IN (#23745) Co-authored-by: Tim Baek Co-authored-by: joaoback <156559121+joaoback@users.noreply.github.com> --- src/lib/i18n/locales/hi-IN/translation.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index b2d1252c76..7b172c8adf 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -460,8 +460,8 @@ "Create Image": "", "Create Knowledge": "", "Create Model": "", - "Create new key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं", - "Create new secret key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं", + "Create new key": "नई कुंजी बनाएं", + "Create new secret key": "नई गुप्त कुंजी बनाएं", "Create note": "", "Create Note": "", "Create scheduled prompts that run automatically on a recurring basis.": "", @@ -815,7 +815,7 @@ "Entra ID": "", "Environment Variables": "", "Ephemeral": "", - "Error": "चूक", + "Error": "त्रुटि", "ERROR": "", "Error accessing directory": "", "Error accessing Google Drive: {{error}}": "", @@ -845,7 +845,7 @@ "Explore the cosmos": "", "Explored": "", "Exploring": "", - "Export": "निर्यातित माल", + "Export": "निर्यात करें", "Export All Archived Chats": "", "Export All Chats (All Users)": "सभी चैट निर्यात करें (सभी उपयोगकर्ताओं की)", "Export as CSV": "", @@ -1093,7 +1093,7 @@ "Includes SharePoint": "SharePoint शामिल है", "Increase UI Scale": "", "Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive.": "", - "Info": "सूचना-विषयक", + "Info": "जानकारी", "Initials": "", "Inject file content into conversation context": "", "Inject the entire content as context for comprehensive processing, this is recommended for complex queries.": "", @@ -1126,7 +1126,7 @@ "Jina API Base URL": "", "Jina API Key": "", "join our Discord for help.": "मदद के लिए हमारे डिस्कोर्ड में शामिल हों।", - "JSON": "ज्ञान प्रकार", + "JSON": "JSON", "JSON Preview": "JSON पूर्वावलोकन", "JSON Spec": "", "July": "जुलाई", @@ -1192,7 +1192,7 @@ "lexical": "", "License": "", "Lift List": "", - "Light": "सुन", + "Light": "हल्का", "Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "", "Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "", "List": "", @@ -1427,7 +1427,7 @@ "October": "अक्टूबर", "Off": "बंद", "Okay, Let's Go!": "ठीक है, चलिए चलते हैं!", - "OLED Dark": "OLEDescuro", + "OLED Dark": "OLED डार्क", "Ollama": "Ollama", "Ollama API": "ओलामा एपीआई", "Ollama API settings updated": "", @@ -1533,7 +1533,7 @@ "Pipelines Valves": "पाइपलाइन वाल्व", "Plain text (.md)": "", "Plain text (.txt)": "सादा पाठ (.txt)", - "Playground": "कार्यक्षेत्र", + "Playground": "प्रयोगशाला", "Playwright Timeout (ms)": "", "Playwright WebSocket URL": "", "Please carefully review the following warnings:": "", @@ -2231,7 +2231,7 @@ "You have no shared conversations.": "", "You have shared this chat": "आपने इस चैट को शेयर किया है", "You.com API Key": "", - "You're a helpful assistant.": "आप एक सहायक सहायक हैं", + "You're a helpful assistant.": "आप एक मददगार सहायक हैं।", "You're now logged in.": "अब आप लॉग इन हो गए हैं", "Your Account": "", "Your account status is currently pending activation.": "", From c27aa569c8276e7d40d673ca8d3ee970f49e782e Mon Sep 17 00:00:00 2001 From: joaoback <156559121+joaoback@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:19:54 -0300 Subject: [PATCH 005/119] i18n: add pt-BR translations for newly added UI items and consistency pass (#23743) New **pt-BR** translations for items introduced in the latest releases, plus a consistency/quality pass across existing strings (grammar, tone, capitalization, pluralization). Placeholders and hotkeys preserved. No logic changes. --- src/lib/i18n/locales/pt-BR/translation.json | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 2a4e99c95c..6f898b892d 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -455,7 +455,7 @@ "Create a new note": "Criar uma nova nota", "Create Account": "Criar Conta", "Create Admin Account": "Criar Conta de Administrador", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "Criar e gerenciar automações agendadas", "Create Channel": "Criar Canal", "Create Folder": "Criar Pasta", "Create Image": "Criar imagem", @@ -481,7 +481,7 @@ "Custom Gender": "Gênero personalizado", "Custom Parameter Name": "Nome do parâmetro personalizado", "Custom Parameter Value": "Valor do parâmetro personalizado", - "Daily": "", + "Daily": "Diário", "Daily Messages": "Mensagens Diárias", "Danger Zone": "Zona de Perigo", "Dark": "Escuro", @@ -972,7 +972,7 @@ "Forward": "Encaminhar", "Forwards system user OAuth access token to authenticate": "Encaminha o token de acesso OAuth do usuário do sistema para autenticação", "Forwards system user session credentials to authenticate": "Encaminha as credenciais da sessão do usuário do sistema para autenticação", - "Fr_day_of_week": "", + "Fr_day_of_week": "Sex", "Full Context Mode": "Modo de contexto completo", "Function": "Função", "Function Calling": "Chamada de função", @@ -1047,7 +1047,7 @@ "History": "Histórico", "Home": "Início", "Host": "Servidor", - "Hourly": "", + "Hourly": "A cada hora", "Hourly Messages": "Mensagens por Hora", "How can I help you today?": "Como posso ajudar você hoje?", "How would you rate this response?": "Como você avalia essa resposta?", @@ -1270,7 +1270,7 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Chave de API do Mistral OCR necessária.", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "Seg", "Model": "Modelo", "Model '{{modelName}}' has been successfully downloaded.": "Modelo '{{modelName}}' foi baixado com sucesso.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelo '{{modelTag}}' já está na fila para download.", @@ -1314,7 +1314,7 @@ "Models Sharing": "Compartilhamento de Modelos", "Mojeek": "Mojeek", "Mojeek Search API Key": "Chave de API Mojeek Search", - "Monthly": "", + "Monthly": "Mensal", "More": "Mais", "More Concise": "Mais conciso", "More options": "Mais opções", @@ -1435,7 +1435,7 @@ "Ollama Cloud API Key": "Chave da API Ollama Cloud", "Ollama Version": "Versão Ollama", "On": "Ligado", - "Once": "", + "Once": "Uma vez", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Ativo somente quando a configuração \"Colar texto grande como arquivo\" estiver ativada.", "Only active when the chat input is in focus and an LLM is generating a response.": "Ativo somente quando o campo de entrada do chat está em foco e um LLM está gerando uma resposta.", @@ -1485,7 +1485,7 @@ "or": "ou", "Ordered List": "Lista ordenada", "Other": "Outro", - "out of": "", + "out of": "de", "Output": "Saída", "OUTPUT": "SAÍDA", "Output format": "Formato de saída", @@ -1520,7 +1520,7 @@ "Persistent": "Persistente", "Personalization": "Personalização", "Pin": "Fixar", - "Pin to Sidebar": "", + "Pin to Sidebar": "Fixar na barra lateral", "Pinned": "Fixado", "Pinned Messages": "Mensagens fixadas", "Pinned Models": "Modelos Fixados", @@ -1682,7 +1682,7 @@ "Running": "Executando", "Running...": "Executando...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Executa tarefas de incorporação simultaneamente para acelerar o processamento. Desative se os limites de taxa se tornarem um problema.", - "Sa_day_of_week": "", + "Sa_day_of_week": "Sáb", "Save": "Salvar", "Save & Create": "Salvar e Criar", "Save & Update": "Salvar e Atualizar", @@ -1899,7 +1899,7 @@ "STT Model": "Modelo STT", "STT Settings": "Configurações STT", "Stylized PDF Export": "Exportação de PDF estilizado", - "Su_day_of_week": "", + "Su_day_of_week": "Dom", "Submit question": "Enviar pergunta", "Submit suggestion": "Enviar sugestão", "Subtitle": "Subtítulo", @@ -1945,7 +1945,7 @@ "Text Splitter": "Divisor de Texto", "Text-to-Speech": "Texto-para-Fala", "Text-to-Speech Engine": "Motor de Texto para Fala", - "Th_day_of_week": "", + "Th_day_of_week": "Qui", "Thanks for your feedback!": "Obrigado pelo seu comentário!", "The Application Account DN you bind with for search": "O DN (Distinguished Name) da Conta de Aplicação com a qual você se conecta para pesquisa.", "The base to search for users": "Base para pesquisar usuários.", @@ -2059,7 +2059,7 @@ "TTS Model": "Modelo TTS", "TTS Settings": "Configurações TTS", "TTS Voice": "Voz TTS", - "Tu_day_of_week": "", + "Tu_day_of_week": "Ter", "Type": "Tipo", "Type here...": "Digite aqui...", "Type Hugging Face Resolve (Download) URL": "Digite o URL de download do Hugging Face", @@ -2162,7 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Aviso: Habilitar esta opção permitirá que os usuários executem solicitações agendadas automaticamente.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Aviso: Habilitar isso permitirá que os usuários façam upload de código arbitrário no servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: a execução do Jupyter permite a execução de código arbitrário, o que representa sérios riscos de segurança. Prossiga com extremo cuidado.", - "We_day_of_week": "", + "We_day_of_week": "Qua", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Motor de carregamento da Web", @@ -2179,7 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "A WebUI fará requisições para \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "A WebUI fará requisições para \"{{url}}/api/chat\".", "WebUI will make requests to \"{{url}}/chat/completions\"": "A WebUI fará requisições para \"{{url}}/chat/completions\".", - "Weekly": "", + "Weekly": "Semanal", "What are you trying to achieve?": "O que está tentando alcançar?", "What are you working on?": "No que está trabalhando?", "What is NOT shared:": "O que NÃO é compartilhado:", From 82755acdfdb45b649b61062e80806b92ccc6d695 Mon Sep 17 00:00:00 2001 From: Aleix Dorca Date: Wed, 15 Apr 2026 19:20:13 +0200 Subject: [PATCH 006/119] i18n: Update Catalan translation file (#23741) * i18n: Update catalan translation.json * i18n: Update Catalan translation.json --- src/lib/i18n/locales/ca-ES/translation.json | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index fd49c6ae07..588e88a9ff 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -455,7 +455,7 @@ "Create a new note": "Crear una nova nota", "Create Account": "Crear un compte", "Create Admin Account": "Crear un compte d'Administrador", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "Crear i gestionar les automatitzacions programades", "Create Channel": "Crear un canal", "Create Folder": "Crear carpeta", "Create Image": "Crear imatge", @@ -481,7 +481,7 @@ "Custom Gender": "Gènere personalitzat", "Custom Parameter Name": "Nom del paràmetre personalitzat", "Custom Parameter Value": "Valor del paràmetre personalitzat", - "Daily": "", + "Daily": "Cada dia", "Daily Messages": "Missatges diaris", "Danger Zone": "Zona de perill", "Dark": "Fosc", @@ -972,7 +972,7 @@ "Forward": "Endavant", "Forwards system user OAuth access token to authenticate": "Reenvia el testimoni d'accés OAuth de l'usuari del sistema per autenticar-se.", "Forwards system user session credentials to authenticate": "Envia les credencials de l'usuari del sistema per autenticar", - "Fr_day_of_week": "", + "Fr_day_of_week": "Divendres", "Full Context Mode": "Mode de context complert", "Function": "Funció", "Function Calling": "Crida a funcions", @@ -1047,7 +1047,7 @@ "History": "Historial", "Home": "Inici", "Host": "Servidor", - "Hourly": "", + "Hourly": "Cada hora", "Hourly Messages": "Missatges horaris", "How can I help you today?": "Com et puc ajudar avui?", "How would you rate this response?": "Com avaluaries aquesta resposta?", @@ -1270,7 +1270,7 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "És necessària la clau API de Mistral OCR", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "Dilluns", "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.", @@ -1314,7 +1314,7 @@ "Models Sharing": "Compartir els models", "Mojeek": "Mojeek", "Mojeek Search API Key": "Clau API de Mojeek Search", - "Monthly": "", + "Monthly": "Cada mes", "More": "Més", "More Concise": "Més precís", "More options": "Més opcions", @@ -1435,7 +1435,7 @@ "Ollama Cloud API Key": "Clau API d'Ollama Cloud", "Ollama Version": "Versió d'Ollama", "On": "Activat", - "Once": "", + "Once": "Una vegada", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Només està actiu quan l'opció \"Enganxa text gran com a fitxer\" està activada.", "Only active when the chat input is in focus and an LLM is generating a response.": "Només s'activa quan l'entrada del xat està en focus i un LLM està generant una resposta.", @@ -1520,7 +1520,7 @@ "Persistent": "Persistent", "Personalization": "Personalització", "Pin": "Fixar", - "Pin to Sidebar": "", + "Pin to Sidebar": "Fixar a la barra lateral", "Pinned": "Fixat", "Pinned Messages": "Missatges fixats", "Pinned Models": "Models fixats", @@ -1682,7 +1682,7 @@ "Running": "S'està executant", "Running...": "S'està executant...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Executa tasques d'incrustació simultàniament per accelerar el processament. Desactiva-ho si els límits de velocitat es converteixen en un problema.", - "Sa_day_of_week": "", + "Sa_day_of_week": "Dissabte", "Save": "Desar", "Save & Create": "Desar i crear", "Save & Update": "Desar i actualitzar", @@ -1899,7 +1899,7 @@ "STT Model": "Model SST", "STT Settings": "Preferències de STT", "Stylized PDF Export": "Exportació en PDF estilitzat", - "Su_day_of_week": "", + "Su_day_of_week": "Diumenge", "Submit question": "Enviar la pregunta", "Submit suggestion": "Enviar un suggeriment", "Subtitle": "Subtítol", @@ -1945,7 +1945,7 @@ "Text Splitter": "Separador de text", "Text-to-Speech": "Text-a-veu", "Text-to-Speech Engine": "Motor de text a veu", - "Th_day_of_week": "", + "Th_day_of_week": "Dijous", "Thanks for your feedback!": "Gràcies pel teu comentari!", "The Application Account DN you bind with for search": "El DN del compte d'aplicació per realitzar la cerca", "The base to search for users": "La base per cercar usuaris", @@ -2059,7 +2059,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Preferències de TTS", "TTS Voice": "Veu TTS", - "Tu_day_of_week": "", + "Tu_day_of_week": "Dimarts", "Type": "Tipus", "Type here...": "Escriu aquí...", "Type Hugging Face Resolve (Download) URL": "Escriu la URL de Resolució (Descàrrega) de Hugging Face", @@ -2162,7 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Avís: Si actives aquesta opció, els usuaris podran executar sol·licituds programades automàticament.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Avís: Habilitar això permetrà als usuaris penjar codi arbitrari al servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Avís: l'execució de Jupyter permet l'execució de codi arbitrari, la qual cosa comporta greus riscos de seguretat; procediu amb extrema precaució.", - "We_day_of_week": "", + "We_day_of_week": "Dimecres", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "Motor de càrrega Web", @@ -2179,7 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI farà peticions a \"{{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\"", - "Weekly": "", + "Weekly": "Cada setmana", "What are you trying to achieve?": "Què intentes aconseguir?", "What are you working on?": "En què estàs treballant?", "What is NOT shared:": "Què no es comparteix", From a4251d7e45f533b2a05b4a35cc68903b07360b39 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:21:28 +0200 Subject: [PATCH 007/119] Update translation.json (#23737) --- src/lib/i18n/locales/de-DE/translation.json | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 58171851e9..1fbfdba8ce 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -454,7 +454,7 @@ "Create a new note": "Neue Notiz erstellen", "Create Account": "Konto erstellen", "Create Admin Account": "Admin-Konto erstellen", - "Create and manage scheduled automations": "", + "Create and manage scheduled automations": "Erstelle und manage geplante Automatisierungen", "Create Channel": "Kanal erstellen", "Create Folder": "Ordner erstellen", "Create Image": "Bild erstellen", @@ -480,7 +480,7 @@ "Custom Gender": "Benutzerdefiniertes Geschlecht", "Custom Parameter Name": "Name des benutzerdef. Parameters", "Custom Parameter Value": "Wert des benutzerdef. Parameters", - "Daily": "", + "Daily": "Täglich", "Daily Messages": "Tägliche Nachrichten", "Danger Zone": "Gefahrenzone", "Dark": "Dunkel", @@ -971,7 +971,7 @@ "Forward": "Weiterleiten", "Forwards system user OAuth access token to authenticate": "Leitet OAuth-Zugriffstoken des Systembenutzers zur Authentifizierung weiter", "Forwards system user session credentials to authenticate": "Leitet Sitzungsdaten des Systembenutzers zur Authentifizierung weiter", - "Fr_day_of_week": "", + "Fr_day_of_week": "Fr", "Full Context Mode": "Vollkontext-Modus", "Function": "Funktion", "Function Calling": "Funktionsaufruf", @@ -1046,7 +1046,7 @@ "History": "History", "Home": "Startseite", "Host": "Host", - "Hourly": "", + "Hourly": "Stündlich", "Hourly Messages": "Stündliche Nachrichten", "How can I help you today?": "Wie kann ich Ihnen heute helfen?", "How would you rate this response?": "Wie bewerten Sie diese Antwort?", @@ -1269,7 +1269,7 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral-OCR-API-Schlüssel erforderlich.", "MistralAI": "MistralAI", - "Mo_day_of_week": "", + "Mo_day_of_week": "Mo", "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.", @@ -1313,7 +1313,7 @@ "Models Sharing": "Modelle teilen", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search API-Schlüssel", - "Monthly": "", + "Monthly": "Monatlich", "More": "Mehr", "More Concise": "Kürzer", "More options": "Mehr Optionen", @@ -1434,7 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API-Schlüssel", "Ollama Version": "Ollama-Version", "On": "Ein", - "Once": "", + "Once": "Einmalig", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Nur aktiv, wenn die Einstellung „Großen Text als Datei einfügen“ aktiviert ist.", "Only active when the chat input is in focus and an LLM is generating a response.": "Nur aktiv, wenn das Chat-Eingabefeld fokussiert ist und ein LLM eine Antwort generiert.", @@ -1519,7 +1519,7 @@ "Persistent": "Persistent", "Personalization": "Personalisierung", "Pin": "Anheften", - "Pin to Sidebar": "", + "Pin to Sidebar": "An Seitenleiste anheften", "Pinned": "Angeheftet", "Pinned Messages": "Angeheftete Nachrichten", "Pinned Models": "Angepinnte Modelle", @@ -1680,7 +1680,7 @@ "Running": "Läuft", "Running...": "Läuft...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Führt Embedding-Aufgaben parallel aus, um die Verarbeitung zu beschleunigen. Deaktivieren Sie dies, falls Rate-Limits oder Ressourcenprobleme auftreten.", - "Sa_day_of_week": "", + "Sa_day_of_week": "Sa", "Save": "Speichern", "Save & Create": "Speichern & Erstellen", "Save & Update": "Speichern & Aktualisieren", @@ -1897,7 +1897,7 @@ "STT Model": "STT-Modell", "STT Settings": "STT-Einstellungen", "Stylized PDF Export": "Stilisierter PDF-Export", - "Su_day_of_week": "", + "Su_day_of_week": "So", "Submit question": "Frage absenden", "Submit suggestion": "Vorschlag absenden", "Subtitle": "Untertitel", @@ -1943,7 +1943,7 @@ "Text Splitter": "Text-Splitter", "Text-to-Speech": "Text-zu-Sprache", "Text-to-Speech Engine": "Text-zu-Sprache-Engine", - "Th_day_of_week": "", + "Th_day_of_week": "Do", "Thanks for your feedback!": "Danke für Ihr Feedback!", "The Application Account DN you bind with for search": "Der Anwendungs-Konto-DN für die Suche", "The base to search for users": "Die Basis, in der nach Benutzern gesucht wird", @@ -2057,7 +2057,7 @@ "TTS Model": "TTS-Modell", "TTS Settings": "TTS-Einstellungen", "TTS Voice": "TTS-Stimme", - "Tu_day_of_week": "", + "Tu_day_of_week": "Di", "Type": "Typ", "Type here...": "Hier eingeben...", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Download) URL eingeben", @@ -2160,7 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Warnung: Wenn Sie dies aktivieren, können Nutzer geplante Prompts automatisch ausführen.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Warnung: Wenn Sie dies aktivieren, können Benutzer beliebigen Code auf den Server hochladen.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Warnung: Die Jupyter-Ausführung ermöglicht beliebige Codeausführung und birgt erhebliche Sicherheitsrisiken – gehen Sie mit äußerster Vorsicht vor.", - "We_day_of_week": "", + "We_day_of_week": "Mi", "Web": "Web", "Web API": "Web-API", "Web Loader Engine": "Web-Loader-Engine", @@ -2177,7 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI wird Anfragen an \"{{url}}\" senden", "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", - "Weekly": "", + "Weekly": "Wöchentlich", "What are you trying to achieve?": "Was möchten Sie erreichen?", "What are you working on?": "Woran arbeiten Sie?", "What is NOT shared:": "Was NICHT geteilt wird:", From 2f9e326dba3b1087932cb6b8075ed1881bd1c6d6 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 15 Apr 2026 10:26:47 -0700 Subject: [PATCH 008/119] refac --- backend/open_webui/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index fbabd32361..08323be125 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -375,7 +375,7 @@ else: except Exception: DATABASE_POOL_RECYCLE = 3600 -DATABASE_ENABLE_SQLITE_WAL = os.environ.get('DATABASE_ENABLE_SQLITE_WAL', 'False').lower() == 'true' +DATABASE_ENABLE_SQLITE_WAL = os.environ.get('DATABASE_ENABLE_SQLITE_WAL', 'True').lower() == 'true' DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = os.environ.get('DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL', None) if DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL is not None: From 70a6a24f143b221c787bc50b72582ee1e0c2dac0 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 15 Apr 2026 10:37:59 -0700 Subject: [PATCH 009/119] refac --- backend/open_webui/utils/middleware.py | 85 +++++++++++++++++++------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 0d330ff31f..89f8e8109e 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -3067,6 +3067,10 @@ async def outlet_filter_handler(ctx): Replaces the separate POST /api/chat/completed round-trip. Persists outlet-modified content to DB and emits a chat:outlet event so the frontend can sync its in-memory state. + + For temp chats (local: prefix), messages are built from form_data + plus the assistant response message stored in ctx['assistant_message'], + since temp chats have no DB-persisted history. """ request = ctx['request'] user = ctx['user'] @@ -3078,17 +3082,43 @@ async def outlet_filter_handler(ctx): chat_id = metadata.get('chat_id', '') message_id = metadata.get('message_id') - if not chat_id or chat_id.startswith('local:') or not message_id: + if not chat_id or not message_id: return - try: - messages_map = await Chats.get_messages_map_by_chat_id(chat_id) - if not messages_map: - return + is_temp_chat = chat_id.startswith('local:') - message_list = get_message_list(messages_map, message_id) - if not message_list: - return + try: + messages_map = None + + if is_temp_chat: + # Temp chats have no DB record — build message list from + # the in-memory form_data plus the assistant response. + form_messages = ctx.get('form_data', {}).get('messages', []) + assistant_message = ctx.get('assistant_message', {}) + + message_list = [ + { + 'role': m.get('role'), + 'content': m.get('content', ''), + } + for m in form_messages + ] + + # Append the full assistant message (content, output, usage, etc.) + if assistant_message: + message_list.append({ + 'id': message_id, + 'role': 'assistant', + **assistant_message, + }) + else: + messages_map = await Chats.get_messages_map_by_chat_id(chat_id) + if not messages_map: + return + + message_list = get_message_list(messages_map, message_id) + if not message_list: + return model_id = model.get('id') if isinstance(model, dict) else model @@ -3101,6 +3131,7 @@ async def outlet_filter_handler(ctx): 'content': m.get('content', ''), 'info': m.get('info'), 'timestamp': m.get('timestamp'), + **({'output': m['output']} if m.get('output') else {}), **({'usage': m['usage']} if m.get('usage') else {}), **({'sources': m['sources']} if m.get('sources') else {}), } @@ -3141,20 +3172,22 @@ async def outlet_filter_handler(ctx): ) # Persist outlet-modified content and notify frontend + # (skip DB persistence for temp chats — they have no DB record) if outlet_result and outlet_result.get('messages'): - for msg in outlet_result['messages']: - msg_id = msg.get('id') - if msg_id and msg_id in messages_map: - original = messages_map[msg_id] - if original.get('content') != msg.get('content'): - await Chats.upsert_message_to_chat_by_id_and_message_id( - chat_id, - msg_id, - { - 'content': msg['content'], - 'originalContent': original.get('content'), - }, - ) + if not is_temp_chat and messages_map: + for message in outlet_result['messages']: + outlet_message_id = message.get('id') + if outlet_message_id and outlet_message_id in messages_map: + original_message = messages_map[outlet_message_id] + if original_message.get('content') != message.get('content'): + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, + outlet_message_id, + { + 'content': message['content'], + 'originalContent': original_message.get('content'), + }, + ) if event_emitter: await event_emitter( @@ -3288,6 +3321,11 @@ async def non_streaming_chat_response_handler(response, ctx): ) await background_tasks_handler(ctx) + ctx['assistant_message'] = { + 'content': content, + 'output': response_output, + **({'usage': usage} if usage else {}), + } await outlet_filter_handler(ctx) response = build_response_object(response, merge_events_into_response(response_data, events)) @@ -4800,6 +4838,11 @@ async def streaming_chat_response_handler(response, ctx): ) await background_tasks_handler(ctx) + ctx['assistant_message'] = { + 'content': serialize_output(output), + 'output': output, + **({'usage': usage} if usage else {}), + } await outlet_filter_handler(ctx) except asyncio.CancelledError: log.warning('Task was cancelled!') From 4d2f18981051205016bd24d39521e25a33581225 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 08:35:45 +0900 Subject: [PATCH 010/119] feat: add RAG_RERANKING_BATCH_SIZE configuration option Add configurable reranker batch size (env var RAG_RERANKING_BATCH_SIZE, default 32) following the same pattern as RAG_EMBEDDING_BATCH_SIZE. - config.py: PersistentConfig for RAG_RERANKING_BATCH_SIZE - main.py: import, state init, pass to get_reranking_function - colbert.py: accept batch_size param in predict() (was hardcoded 32) - utils.py: get_reranking_function passes batch_size at call time - retrieval.py: expose in config GET/POST endpoints and ConfigForm - Documents.svelte: add Reranking Batch Size input in admin settings Closes #23730 --- backend/open_webui/config.py | 6 ++++++ backend/open_webui/main.py | 3 +++ backend/open_webui/retrieval/models/colbert.py | 6 +++--- backend/open_webui/retrieval/utils.py | 4 ++-- backend/open_webui/routers/retrieval.py | 9 +++++++++ .../components/admin/Settings/Documents.svelte | 17 +++++++++++++++++ 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 65f29a4ad7..a68720a7c0 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -2965,6 +2965,12 @@ RAG_RERANKING_MODEL_TRUST_REMOTE_CODE = ( os.environ.get('RAG_RERANKING_MODEL_TRUST_REMOTE_CODE', 'True').lower() == 'true' ) +RAG_RERANKING_BATCH_SIZE = PersistentConfig( + 'RAG_RERANKING_BATCH_SIZE', + 'rag.reranking_batch_size', + int(os.environ.get('RAG_RERANKING_BATCH_SIZE', '32')), +) + RAG_EXTERNAL_RERANKER_URL = PersistentConfig( 'RAG_EXTERNAL_RERANKER_URL', 'rag.external_reranker_url', diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 63580f990d..c183a9323c 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -248,6 +248,7 @@ from open_webui.config import ( RAG_EXTERNAL_RERANKER_URL, RAG_EXTERNAL_RERANKER_API_KEY, RAG_EXTERNAL_RERANKER_TIMEOUT, + RAG_RERANKING_BATCH_SIZE, RAG_RERANKING_MODEL_AUTO_UPDATE, RAG_RERANKING_MODEL_TRUST_REMOTE_CODE, RAG_EMBEDDING_ENGINE, @@ -1044,6 +1045,7 @@ app.state.config.RAG_RERANKING_MODEL = RAG_RERANKING_MODEL app.state.config.RAG_EXTERNAL_RERANKER_URL = RAG_EXTERNAL_RERANKER_URL app.state.config.RAG_EXTERNAL_RERANKER_API_KEY = RAG_EXTERNAL_RERANKER_API_KEY app.state.config.RAG_EXTERNAL_RERANKER_TIMEOUT = RAG_EXTERNAL_RERANKER_TIMEOUT +app.state.config.RAG_RERANKING_BATCH_SIZE = RAG_RERANKING_BATCH_SIZE app.state.config.RAG_TEMPLATE = RAG_TEMPLATE @@ -1193,6 +1195,7 @@ app.state.RERANKING_FUNCTION = get_reranking_function( app.state.config.RAG_RERANKING_ENGINE, app.state.config.RAG_RERANKING_MODEL, reranking_function=app.state.rf, + reranking_batch_size=app.state.config.RAG_RERANKING_BATCH_SIZE, ) ######################################## diff --git a/backend/open_webui/retrieval/models/colbert.py b/backend/open_webui/retrieval/models/colbert.py index d122291ec5..ceb41824e3 100644 --- a/backend/open_webui/retrieval/models/colbert.py +++ b/backend/open_webui/retrieval/models/colbert.py @@ -59,14 +59,14 @@ class ColBERT(BaseReranker): return normalized_scores.detach().cpu().numpy().astype(np.float32) - def predict(self, sentences): + def predict(self, sentences, batch_size=32): query = sentences[0][0] docs = [i[1] for i in sentences] # Embedding the documents - embedded_docs = self.ckpt.docFromText(docs, bsize=32)[0] + embedded_docs = self.ckpt.docFromText(docs, bsize=batch_size)[0] # Embedding the queries - embedded_queries = self.ckpt.queryFromText([query], bsize=32) + embedded_queries = self.ckpt.queryFromText([query], bsize=batch_size) embedded_query = embedded_queries[0] # Calculate retrieval scores for the query against all documents diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 3638409eee..35a72f4df1 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -919,7 +919,7 @@ async def generate_embeddings( return embeddings[0] if isinstance(text, str) else embeddings -def get_reranking_function(reranking_engine, reranking_model, reranking_function): +def get_reranking_function(reranking_engine, reranking_model, reranking_function, reranking_batch_size=32): if reranking_function is None: return None if reranking_engine == 'external': @@ -928,7 +928,7 @@ def get_reranking_function(reranking_engine, reranking_model, reranking_function ) else: return lambda query, documents, user=None: reranking_function.predict( - [(query, doc.page_content) for doc in documents] + [(query, doc.page_content) for doc in documents], batch_size=int(reranking_batch_size) ) diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 0c30122022..fd291a2803 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -487,6 +487,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)): # Reranking settings 'RAG_RERANKING_MODEL': request.app.state.config.RAG_RERANKING_MODEL, 'RAG_RERANKING_ENGINE': request.app.state.config.RAG_RERANKING_ENGINE, + 'RAG_RERANKING_BATCH_SIZE': request.app.state.config.RAG_RERANKING_BATCH_SIZE, 'RAG_EXTERNAL_RERANKER_URL': request.app.state.config.RAG_EXTERNAL_RERANKER_URL, 'RAG_EXTERNAL_RERANKER_API_KEY': request.app.state.config.RAG_EXTERNAL_RERANKER_API_KEY, 'RAG_EXTERNAL_RERANKER_TIMEOUT': request.app.state.config.RAG_EXTERNAL_RERANKER_TIMEOUT, @@ -694,6 +695,7 @@ class ConfigForm(BaseModel): # Reranking settings RAG_RERANKING_MODEL: Optional[str] = None RAG_RERANKING_ENGINE: Optional[str] = None + RAG_RERANKING_BATCH_SIZE: Optional[int] = None RAG_EXTERNAL_RERANKER_URL: Optional[str] = None RAG_EXTERNAL_RERANKER_API_KEY: Optional[str] = None RAG_EXTERNAL_RERANKER_TIMEOUT: Optional[str] = None @@ -940,6 +942,12 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend else request.app.state.config.RAG_EXTERNAL_RERANKER_TIMEOUT ) + request.app.state.config.RAG_RERANKING_BATCH_SIZE = ( + form_data.RAG_RERANKING_BATCH_SIZE + if form_data.RAG_RERANKING_BATCH_SIZE is not None + else request.app.state.config.RAG_RERANKING_BATCH_SIZE + ) + log.info( f'Updating reranking model: {request.app.state.config.RAG_RERANKING_MODEL} to {form_data.RAG_RERANKING_MODEL}' ) @@ -967,6 +975,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend request.app.state.config.RAG_RERANKING_ENGINE, request.app.state.config.RAG_RERANKING_MODEL, request.app.state.rf, + reranking_batch_size=request.app.state.config.RAG_RERANKING_BATCH_SIZE, ) except Exception as e: log.error(f'Error loading reranking model: {e}') diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index ece64afd54..eeb6b18b10 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -1185,6 +1185,23 @@ {/if} +
+
+ {$i18n.t('Reranking Batch Size')} +
+ +
+ +
+
+
{$i18n.t('Top K')}
From 2e52ad8ff2f8d9ed9f38f76e9bc19c8f92d91fc3 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 10:16:32 +0900 Subject: [PATCH 011/119] refac: shared chat --- .../c1d2e3f4a5b6_add_shared_chat_table.py | 164 ++++++++++++ backend/open_webui/models/chats.py | 179 +++++-------- backend/open_webui/models/shared_chats.py | 222 ++++++++++++++++ backend/open_webui/routers/chats.py | 241 ++++++++++++++---- .../open_webui/utils/access_control/files.py | 16 +- src/lib/apis/chats/index.ts | 71 ++++++ src/lib/components/chat/ShareChatModal.svelte | 166 +++++++----- 7 files changed, 818 insertions(+), 241 deletions(-) create mode 100644 backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py create mode 100644 backend/open_webui/models/shared_chats.py diff --git a/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py b/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py new file mode 100644 index 0000000000..ca2f9e7cd3 --- /dev/null +++ b/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py @@ -0,0 +1,164 @@ +"""Add shared_chat table and migrate existing shares + +Revision ID: c1d2e3f4a5b6 +Revises: e1f2a3b4c5d6 +Create Date: 2026-04-16 23:00:00.000000 + +""" + +import time +import uuid + +from alembic import op +import sqlalchemy as sa + +revision = 'c1d2e3f4a5b6' +down_revision = 'e1f2a3b4c5d6' +branch_labels = None +depends_on = None + +# Lightweight table references for data migration (no ORM models needed) +chat_t = sa.table( + 'chat', + sa.column('id', sa.Text), + sa.column('user_id', sa.Text), + sa.column('title', sa.Text), + sa.column('chat', sa.JSON), + sa.column('share_id', sa.Text), + sa.column('created_at', sa.BigInteger), + sa.column('updated_at', sa.BigInteger), + sa.column('archived', sa.Boolean), + sa.column('meta', sa.JSON), +) + +shared_chat_t = sa.table( + 'shared_chat', + sa.column('id', sa.Text), + sa.column('chat_id', sa.Text), + sa.column('user_id', sa.Text), + sa.column('title', sa.Text), + sa.column('chat', sa.JSON), + sa.column('created_at', sa.BigInteger), + sa.column('updated_at', sa.BigInteger), +) + +chat_message_t = sa.table( + 'chat_message', + sa.column('chat_id', sa.Text), +) + +access_grant_t = sa.table( + 'access_grant', + sa.column('id', sa.Text), + sa.column('resource_type', sa.Text), + sa.column('resource_id', sa.Text), + sa.column('principal_type', sa.Text), + sa.column('principal_id', sa.Text), + sa.column('permission', sa.Text), + sa.column('created_at', sa.BigInteger), +) + + +def upgrade(): + conn = op.get_bind() + + # 1. Create shared_chat table + op.create_table( + 'shared_chat', + sa.Column('id', sa.Text(), primary_key=True), + sa.Column('chat_id', sa.Text(), sa.ForeignKey('chat.id', ondelete='CASCADE'), nullable=False), + sa.Column('user_id', sa.Text(), nullable=False), + sa.Column('title', sa.Text(), nullable=True), + sa.Column('chat', sa.JSON(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=True), + sa.Column('updated_at', sa.BigInteger(), nullable=True), + ) + + # 2. Migrate existing shared-* rows + shared_rows = conn.execute( + sa.select( + chat_t.c.id, + chat_t.c.user_id, + chat_t.c.title, + chat_t.c.chat, + chat_t.c.created_at, + chat_t.c.updated_at, + ).where(chat_t.c.user_id.like('shared-%')) + ).fetchall() + + for row in shared_rows: + share_token = row.id + original_chat_id = row.user_id.replace('shared-', '', 1) + + # Verify original chat still exists + original = conn.execute( + sa.select(chat_t.c.user_id).where(chat_t.c.id == original_chat_id) + ).fetchone() + + if not original: + continue + + # Insert snapshot into shared_chat + conn.execute(shared_chat_t.insert().values( + id=share_token, + chat_id=original_chat_id, + user_id=original.user_id, + title=row.title, + chat=row.chat, + created_at=row.created_at, + updated_at=row.updated_at, + )) + + # Create user:*:read grant for backward compat + conn.execute(access_grant_t.insert().values( + id=str(uuid.uuid4()), + resource_type='shared_chat', + resource_id=original_chat_id, + principal_type='user', + principal_id='*', + permission='read', + created_at=row.created_at or int(time.time()), + )) + + # 3. Clean up old phantom rows + conn.execute( + chat_message_t.delete().where( + chat_message_t.c.chat_id.in_( + sa.select(chat_t.c.id).where(chat_t.c.user_id.like('shared-%')) + ) + ) + ) + conn.execute(chat_t.delete().where(chat_t.c.user_id.like('shared-%'))) + + +def downgrade(): + conn = op.get_bind() + + shared_rows = conn.execute( + sa.select( + shared_chat_t.c.id, + shared_chat_t.c.chat_id, + shared_chat_t.c.user_id, + shared_chat_t.c.title, + shared_chat_t.c.chat, + shared_chat_t.c.created_at, + shared_chat_t.c.updated_at, + ) + ).fetchall() + + for row in shared_rows: + conn.execute(chat_t.insert().values( + id=row.id, + user_id=f'shared-{row.chat_id}', + title=row.title, + chat=row.chat, + created_at=row.created_at, + updated_at=row.updated_at, + archived=False, + meta={}, + )) + + conn.execute( + access_grant_t.delete().where(access_grant_t.c.resource_type == 'shared_chat') + ) + op.drop_table('shared_chat') diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 3bcfdce03f..0e1c6bef9d 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -555,77 +555,51 @@ class ChatTable: async def insert_shared_chat_by_chat_id( self, chat_id: str, db: Optional[AsyncSession] = None ) -> Optional[ChatModel]: + """Create a shared snapshot for a chat. Returns the original chat with share_id set.""" + from open_webui.models.shared_chats import SharedChats + async with get_async_db_context(db) as db: - # Get the existing chat to share chat = await db.get(Chat, chat_id) - # Check if chat exists if not chat: return None - # Check if the chat is already shared - if chat.share_id: - return await self.get_chat_by_id_and_user_id(chat.share_id, 'shared', db=db) - # Create a new chat with the same data, but with a new ID - shared_chat = ChatModel( - **{ - 'id': str(uuid.uuid4()), - 'user_id': f'shared-{chat_id}', - 'title': chat.title, - 'chat': chat.chat, - 'meta': chat.meta, - 'pinned': chat.pinned, - 'folder_id': chat.folder_id, - 'created_at': chat.created_at, - 'updated_at': int(time.time()), - } - ) - shared_result = Chat(**shared_chat.model_dump()) - db.add(shared_result) - await db.commit() - await db.refresh(shared_result) - # Update the original chat with the share_id - await db.execute(update(Chat).filter_by(id=chat_id).values(share_id=shared_chat.id)) + # If already shared, just update the existing snapshot + if chat.share_id: + return await self.update_shared_chat_by_chat_id(chat_id, db=db) + + shared = await SharedChats.create(chat_id, chat.user_id, db=db) + if not shared: + return None + + # Set share_id on the original chat + chat.share_id = shared.id await db.commit() - return shared_chat if shared_result else None + await db.refresh(chat) + return ChatModel.model_validate(chat) async def update_shared_chat_by_chat_id( self, chat_id: str, db: Optional[AsyncSession] = None ) -> Optional[ChatModel]: + """Re-snapshot the shared chat with current chat data.""" + from open_webui.models.shared_chats import SharedChats + try: async with get_async_db_context(db) as db: chat = await db.get(Chat, chat_id) - result = await db.execute(select(Chat).filter_by(user_id=f'shared-{chat_id}')) - shared_chat = result.scalars().first() - - if shared_chat is None: + if not chat or not chat.share_id: return await self.insert_shared_chat_by_chat_id(chat_id, db=db) - shared_chat.title = chat.title - shared_chat.chat = chat.chat - shared_chat.meta = chat.meta - shared_chat.pinned = chat.pinned - shared_chat.folder_id = chat.folder_id - shared_chat.updated_at = int(time.time()) - await db.commit() - await db.refresh(shared_chat) - - return ChatModel.model_validate(shared_chat) + await SharedChats.update(chat.share_id, db=db) + return ChatModel.model_validate(chat) except Exception: return None async def delete_shared_chat_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> bool: + """Delete shared snapshot for a chat.""" + from open_webui.models.shared_chats import SharedChats + try: - async with get_async_db_context(db) as db: - # Get shared chat IDs - result = await db.execute(select(Chat.id).filter_by(user_id=f'shared-{chat_id}')) - shared_ids = [row[0] for row in result.all()] - - if shared_ids: - await db.execute(delete(ChatMessage).filter(ChatMessage.chat_id.in_(shared_ids))) - await db.execute(delete(Chat).filter_by(user_id=f'shared-{chat_id}')) - await db.commit() - - return True + return await SharedChats.delete_by_chat_id(chat_id, db=db) except Exception: return False @@ -746,53 +720,12 @@ class ChatTable: limit: int = 50, db: Optional[AsyncSession] = None, ) -> list[SharedChatResponse]: - async with get_async_db_context(db) as db: - stmt = ( - select(Chat.id, Chat.title, Chat.share_id, Chat.updated_at, Chat.created_at) - .filter_by(user_id=user_id) - .filter(Chat.share_id.isnot(None)) - ) + """Delegate to SharedChats for listing shared chats by user.""" + from open_webui.models.shared_chats import SharedChats - if filter: - query_key = filter.get('query') - if query_key: - stmt = stmt.filter(Chat.title.ilike(f'%{query_key}%')) - - order_by = filter.get('order_by') - direction = filter.get('direction') - - if order_by and direction: - if not getattr(Chat, order_by, None): - raise ValueError('Invalid order_by field') - - if direction.lower() == 'asc': - stmt = stmt.order_by(getattr(Chat, order_by).asc(), Chat.id) - elif direction.lower() == 'desc': - stmt = stmt.order_by(getattr(Chat, order_by).desc(), Chat.id) - else: - raise ValueError('Invalid direction for ordering') - else: - stmt = stmt.order_by(Chat.updated_at.desc(), Chat.id) - - if skip: - stmt = stmt.offset(skip) - if limit: - stmt = stmt.limit(limit) - - result = await db.execute(stmt) - all_chats = result.all() - return [ - SharedChatResponse.model_validate( - { - 'id': chat[0], - 'title': chat[1], - 'share_id': chat[2], - 'updated_at': chat[3], - 'created_at': chat[4], - } - ) - for chat in all_chats - ] + return await SharedChats.get_by_user_id( + user_id, filter=filter, skip=skip, limit=limit, db=db + ) async def get_chat_list_by_user_id( self, @@ -925,15 +858,23 @@ class ChatTable: return None async def get_chat_by_share_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[ChatModel]: - try: - async with get_async_db_context(db) as db: - result = await db.execute(select(Chat).filter_by(share_id=id)) - chat = result.scalars().first() + """Look up a shared chat snapshot by its share token.""" + from open_webui.models.shared_chats import SharedChats - if chat: - return await self.get_chat_by_id(id, db=db) - else: - return None + try: + shared = await SharedChats.get_by_id(id, db=db) + if shared: + # Return a ChatModel-compatible view of the snapshot + return ChatModel( + id=shared.id, + user_id=shared.user_id, + title=shared.title, + chat=shared.chat, + created_at=shared.created_at, + updated_at=shared.updated_at, + share_id=shared.id, + ) + return None except Exception: return None @@ -1568,20 +1509,17 @@ class ChatTable: return False async def delete_shared_chats_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> bool: + """Delete all shared chat snapshots created by a user.""" + from open_webui.models.shared_chats import SharedChats, SharedChat as SharedChatTable + try: async with get_async_db_context(db) as db: - result = await db.execute(select(Chat.id).filter_by(user_id=user_id)) - id_rows = result.all() - shared_chat_ids = [f'shared-{row[0]}' for row in id_rows] + # Delete shared_chat rows for this user's chats + await db.execute(delete(SharedChatTable).filter_by(user_id=user_id)) - if shared_chat_ids: - # Get shared chat IDs to delete associated messages - shared_result = await db.execute(select(Chat.id).filter(Chat.user_id.in_(shared_chat_ids))) - shared_ids = [row[0] for row in shared_result.all()] - if shared_ids: - await db.execute(delete(ChatMessage).filter(ChatMessage.chat_id.in_(shared_ids))) - await db.execute(delete(Chat).filter(Chat.user_id.in_(shared_chat_ids))) - await db.commit() + # Clear share_id on all of this user's chats + await db.execute(update(Chat).filter_by(user_id=user_id).values(share_id=None)) + await db.commit() return True except Exception: @@ -1651,16 +1589,15 @@ class ChatTable: except Exception: return False - async def get_shared_chats_by_file_id(self, file_id: str, db: Optional[AsyncSession] = None) -> list[ChatModel]: + async def get_shared_chat_ids_by_file_id(self, file_id: str, db: Optional[AsyncSession] = None) -> list[str]: + """Return IDs of chats that contain this file and have an active share link.""" async with get_async_db_context(db) as db: result = await db.execute( - select(Chat) + select(Chat.id) .join(ChatFile, Chat.id == ChatFile.chat_id) .filter(ChatFile.file_id == file_id, Chat.share_id.isnot(None)) ) - all_chats = result.scalars().all() - - return [ChatModel.model_validate(chat) for chat in all_chats] + return [row[0] for row in result.all()] async def update_chat_tasks_by_id(self, id: str, tasks: list[dict]) -> Optional[ChatModel]: """Update the tasks list on a chat.""" diff --git a/backend/open_webui/models/shared_chats.py b/backend/open_webui/models/shared_chats.py new file mode 100644 index 0000000000..1a042922fb --- /dev/null +++ b/backend/open_webui/models/shared_chats.py @@ -0,0 +1,222 @@ +import logging +import time +import uuid +from typing import Optional + +from sqlalchemy import select, delete +from sqlalchemy.ext.asyncio import AsyncSession +from open_webui.internal.db import Base, JSONField, get_async_db_context + +from pydantic import BaseModel, ConfigDict +from sqlalchemy import BigInteger, Column, ForeignKey, Text, JSON + +log = logging.getLogger(__name__) + +#################### +# SharedChat DB Schema +#################### + + +class SharedChat(Base): + __tablename__ = 'shared_chat' + + id = Column(Text, primary_key=True) # The share token (UUID) — used in /s/{id} URL + chat_id = Column(Text, ForeignKey('chat.id', ondelete='CASCADE'), nullable=False) + user_id = Column(Text, nullable=False) # Who created this share + + title = Column(Text) + chat = Column(JSON) # Snapshot of chat JSON at share time + + created_at = Column(BigInteger) + updated_at = Column(BigInteger) + + +class SharedChatModel(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: str + chat_id: str + user_id: str + + title: str + chat: dict + + created_at: int + updated_at: int + + +class SharedChatResponse(BaseModel): + id: str + chat_id: str + title: str + share_id: Optional[str] = None # Alias for id, for backward compat + updated_at: int + created_at: int + + +#################### +# Table Operations +#################### + + +class SharedChatsTable: + async def create( + self, chat_id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[SharedChatModel]: + """ + Create a snapshot of the chat for link sharing. + Returns the SharedChatModel with the share token as its id. + """ + async with get_async_db_context(db) as db: + from open_webui.models.chats import Chat + + chat = await db.get(Chat, chat_id) + if not chat: + return None + + share_id = str(uuid.uuid4()) + now = int(time.time()) + + shared_chat = SharedChat( + id=share_id, + chat_id=chat_id, + user_id=user_id, + title=chat.title, + chat=chat.chat, + created_at=now, + updated_at=now, + ) + db.add(shared_chat) + await db.commit() + await db.refresh(shared_chat) + + return SharedChatModel.model_validate(shared_chat) + + async def update( + self, share_id: str, db: Optional[AsyncSession] = None + ) -> Optional[SharedChatModel]: + """ + Re-snapshot: update the shared chat with the current state of the original chat. + """ + async with get_async_db_context(db) as db: + from open_webui.models.chats import Chat + + shared_chat = await db.get(SharedChat, share_id) + if not shared_chat: + return None + + chat = await db.get(Chat, shared_chat.chat_id) + if not chat: + return None + + shared_chat.title = chat.title + shared_chat.chat = chat.chat + shared_chat.updated_at = int(time.time()) + + await db.commit() + await db.refresh(shared_chat) + return SharedChatModel.model_validate(shared_chat) + + async def get_by_id( + self, share_id: str, db: Optional[AsyncSession] = None + ) -> Optional[SharedChatModel]: + """Get a shared chat by its share token.""" + async with get_async_db_context(db) as db: + shared_chat = await db.get(SharedChat, share_id) + if shared_chat: + return SharedChatModel.model_validate(shared_chat) + return None + + async def get_by_chat_id( + self, chat_id: str, db: Optional[AsyncSession] = None + ) -> Optional[SharedChatModel]: + """Get the shared chat for a given original chat. Returns the most recent one.""" + async with get_async_db_context(db) as db: + result = await db.execute( + select(SharedChat) + .filter_by(chat_id=chat_id) + .order_by(SharedChat.updated_at.desc()) + .limit(1) + ) + shared_chat = result.scalars().first() + if shared_chat: + return SharedChatModel.model_validate(shared_chat) + return None + + async def get_by_user_id( + self, + user_id: str, + filter: Optional[dict] = None, + skip: int = 0, + limit: int = 50, + db: Optional[AsyncSession] = None, + ) -> list[SharedChatResponse]: + """List all shared chats created by a user.""" + async with get_async_db_context(db) as db: + stmt = select(SharedChat).filter_by(user_id=user_id) + + if filter: + query_key = filter.get('query') + if query_key: + stmt = stmt.filter(SharedChat.title.ilike(f'%{query_key}%')) + + order_by = filter.get('order_by') + direction = filter.get('direction') + + if order_by and direction: + col = getattr(SharedChat, order_by, None) + if not col: + raise ValueError('Invalid order_by field') + if direction.lower() == 'asc': + stmt = stmt.order_by(col.asc()) + elif direction.lower() == 'desc': + stmt = stmt.order_by(col.desc()) + else: + raise ValueError('Invalid direction for ordering') + else: + stmt = stmt.order_by(SharedChat.updated_at.desc()) + + if skip: + stmt = stmt.offset(skip) + if limit: + stmt = stmt.limit(limit) + + result = await db.execute(stmt) + return [ + SharedChatResponse( + id=sc.chat_id, + chat_id=sc.chat_id, + title=sc.title, + share_id=sc.id, + updated_at=sc.updated_at, + created_at=sc.created_at, + ) + for sc in result.scalars().all() + ] + + async def delete_by_id( + self, share_id: str, db: Optional[AsyncSession] = None + ) -> bool: + """Delete a shared chat by its share token.""" + try: + async with get_async_db_context(db) as db: + await db.execute(delete(SharedChat).filter_by(id=share_id)) + await db.commit() + return True + except Exception: + return False + + async def delete_by_chat_id( + self, chat_id: str, db: Optional[AsyncSession] = None + ) -> bool: + """Delete all shared chats for a given original chat.""" + try: + async with get_async_db_context(db) as db: + await db.execute(delete(SharedChat).filter_by(chat_id=chat_id)) + await db.commit() + return True + except Exception: + return False + + +SharedChats = SharedChatsTable() diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 1980d22362..02f9a74662 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -17,13 +17,14 @@ from open_webui.models.chats import ( ChatResponse, Chats, ChatTitleIdResponse, - SharedChatResponse, ChatStatsExport, AggregateChatStats, ChatBody, ChatHistoryStats, MessageStats, ) +from open_webui.models.shared_chats import SharedChats, SharedChatResponse +from open_webui.models.access_grants import AccessGrants from open_webui.models.tags import TagModel, Tags from open_webui.models.folders import Folders from open_webui.internal.db import get_async_session @@ -35,7 +36,7 @@ from pydantic import BaseModel from open_webui.utils.auth import get_admin_user, get_verified_user -from open_webui.utils.access_control import has_permission +from open_webui.utils.access_control import has_permission, filter_allowed_access_grants log = logging.getLogger(__name__) @@ -807,7 +808,7 @@ async def get_shared_session_user_chat_list( if direction: filter['direction'] = direction - return await Chats.get_shared_chat_list_by_user_id( + return await SharedChats.get_by_user_id( user.id, filter=filter, skip=skip, @@ -828,17 +829,32 @@ async def get_shared_chat_by_id( if user.role == 'pending': raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) - if user.role == 'user' or (user.role == 'admin' and not ENABLE_ADMIN_CHAT_ACCESS): - chat = await Chats.get_chat_by_share_id(share_id, db=db) - elif user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS: + if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS: chat = await Chats.get_chat_by_id(share_id, db=db) - - if chat: - return ChatResponse(**chat.model_dump()) - else: + chat = await Chats.get_chat_by_share_id(share_id, db=db) + + if not chat: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) + # Look up the original chat_id to check access grants + shared = await SharedChats.get_by_id(share_id, db=db) + if shared: + has_grant = await AccessGrants.has_access( + user_id=user.id, + resource_type='shared_chat', + resource_id=shared.chat_id, + permission='read', + db=db, + ) + if not has_grant: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + return ChatResponse(**chat.model_dump()) + ############################ # GetChatsByTags @@ -878,11 +894,25 @@ async def get_user_chat_list_by_tag_name( async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) + if not chat: + # Check if user has access via access grants (shared_chat grants) + if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS: + chat = await Chats.get_chat_by_id(id, db=db) + else: + has_grant = await AccessGrants.has_access( + user_id=user.id, + resource_type='shared_chat', + resource_id=id, + permission='read', + db=db, + ) + if has_grant: + chat = await Chats.get_chat_by_id(id, db=db) + if chat: return ChatResponse(**chat.model_dump()) - else: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) ############################ @@ -1158,39 +1188,58 @@ async def clone_shared_chat_by_id( else: chat = await Chats.get_chat_by_share_id(id, db=db) - if chat: - updated_chat = { - **chat.chat, - 'originalChatId': chat.id, - 'branchPointMessageId': chat.chat['history']['currentId'], - 'title': f'Clone of {chat.title}', - } - - chats = await Chats.import_chats( - user.id, - [ - ChatImportForm( - **{ - 'chat': updated_chat, - 'meta': chat.meta, - 'pinned': chat.pinned, - 'folder_id': chat.folder_id, - } - ) - ], - db=db, + if not chat: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.NOT_FOUND, ) - if chats: - chat = chats[0] - return ChatResponse(**chat.model_dump()) - else: + # Enforce access grants + shared = await SharedChats.get_by_id(id, db=db) + if shared and user.role != 'admin': + has_grant = await AccessGrants.has_access( + user_id=user.id, + resource_type='shared_chat', + resource_id=shared.chat_id, + permission='read', + db=db, + ) + if not has_grant: raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=ERROR_MESSAGES.DEFAULT(), + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) + + updated_chat = { + **chat.chat, + 'originalChatId': chat.id, + 'branchPointMessageId': chat.chat['history']['currentId'], + 'title': f'Clone of {chat.title}', + } + + chats = await Chats.import_chats( + user.id, + [ + ChatImportForm( + **{ + 'chat': updated_chat, + 'meta': chat.meta, + 'pinned': chat.pinned, + 'folder_id': chat.folder_id, + } + ) + ], + db=db, + ) + + if chats: + chat = chats[0] + return ChatResponse(**chat.model_dump()) else: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT()) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=ERROR_MESSAGES.DEFAULT(), + ) ############################ @@ -1241,16 +1290,28 @@ async def share_chat_by_id( if chat: if chat.share_id: - shared_chat = await Chats.update_shared_chat_by_chat_id(chat.id, db=db) - return ChatResponse(**shared_chat.model_dump()) + # Re-snapshot existing share + shared = await SharedChats.update(chat.share_id, db=db) + if shared: + # Re-fetch the original chat to return + chat = await Chats.get_chat_by_id(id, db=db) + return ChatResponse(**chat.model_dump()) - shared_chat = await Chats.insert_shared_chat_by_chat_id(chat.id, db=db) - if not shared_chat: + # Create new share + shared = await SharedChats.create(id, user.id, db=db) + if not shared: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=ERROR_MESSAGES.DEFAULT(), ) - return ChatResponse(**shared_chat.model_dump()) + # Set share_id on the original chat + chat = await Chats.update_chat_share_id_by_id(id, shared.id, db=db) + if not chat: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=ERROR_MESSAGES.DEFAULT(), + ) + return ChatResponse(**chat.model_dump()) else: raise HTTPException( @@ -1260,7 +1321,7 @@ async def share_chat_by_id( ############################ -# DeletedSharedChatById +# DeleteSharedChatById ############################ @@ -1273,10 +1334,13 @@ async def delete_shared_chat_by_id( if not chat.share_id: return False - result = await Chats.delete_shared_chat_by_chat_id(id, db=db) - update_result = await Chats.update_chat_share_id_by_id(id, None, db=db) + await SharedChats.delete_by_chat_id(id, db=db) + await Chats.update_chat_share_id_by_id(id, None, db=db) - return result and update_result != None + # Revoke all access grants for this shared chat + await AccessGrants.set_access_grants('shared_chat', id, [], db=db) + + return True else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -1284,6 +1348,85 @@ async def delete_shared_chat_by_id( ) +############################ +# UpdateSharedChatAccessById +############################ + + +class ChatAccessGrantsForm(BaseModel): + access_grants: list[dict] + + +@router.post('/shared/{id}/access/update', response_model=Optional[ChatResponse]) +async def update_shared_chat_access_by_id( + request: Request, + id: str, + form_data: ChatAccessGrantsForm, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) + if not chat: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + if chat.user_id != user.id and user.role != 'admin': + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + form_data.access_grants = await filter_allowed_access_grants( + request.app.state.config.USER_PERMISSIONS, + user.id, + user.role, + form_data.access_grants, + 'sharing.public_chats', + ) + + await AccessGrants.set_access_grants('shared_chat', id, form_data.access_grants, db=db) + + return ChatResponse(**chat.model_dump()) + + +############################ +# GetSharedChatAccessById +############################ + + +@router.get('/shared/{id}/access', response_model=list) +async def get_shared_chat_access_by_id( + id: str, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) + if not chat: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + + if chat.user_id != user.id and user.role != 'admin': + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + grants = await AccessGrants.get_grants_by_resource('shared_chat', id, db=db) + return [ + { + 'id': g.id, + 'principal_type': g.principal_type, + 'principal_id': g.principal_id, + 'permission': g.permission, + } + for g in grants + ] + + ############################ # UpdateChatFolderIdById ############################ diff --git a/backend/open_webui/utils/access_control/files.py b/backend/open_webui/utils/access_control/files.py index 5e7efb5b26..a48dfeb0f1 100644 --- a/backend/open_webui/utils/access_control/files.py +++ b/backend/open_webui/utils/access_control/files.py @@ -66,10 +66,18 @@ async def has_access_to_file( return True # Check if the file is associated with any chats the user has access to - # TODO: Granular access control for chats - chats = await Chats.get_shared_chats_by_file_id(file_id, db=db) - if chats: - return True + shared_chat_ids = await Chats.get_shared_chat_ids_by_file_id(file_id, db=db) + if shared_chat_ids: + accessible_ids = await AccessGrants.get_accessible_resource_ids( + user_id=user.id, + resource_type='shared_chat', + resource_ids=shared_chat_ids, + permission='read', + user_group_ids=user_group_ids, + db=db, + ) + if accessible_ids: + return True # Check if the file is directly attached to a shared workspace model for model in await Models.get_models_by_user_id(user.id, permission=access_type, db=db): diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index e16746707a..751fc823f2 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -953,6 +953,77 @@ export const deleteSharedChatById = async (token: string, id: string) => { return res; }; +export const updateChatAccessGrants = async ( + token: string, + id: string, + accessGrants: object[] +) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/shared/${id}/access/update`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + access_grants: accessGrants + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const getChatAccessGrants = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/shared/${id}/access`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const updateChatById = async (token: string, id: string, chat: object) => { let error = null; diff --git a/src/lib/components/chat/ShareChatModal.svelte b/src/lib/components/chat/ShareChatModal.svelte index 1c02f6481d..c48519e4ca 100644 --- a/src/lib/components/chat/ShareChatModal.svelte +++ b/src/lib/components/chat/ShareChatModal.svelte @@ -3,24 +3,32 @@ import { models, config } from '$lib/stores'; import { toast } from 'svelte-sonner'; - import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats'; + import { + deleteSharedChatById, + getChatById, + shareChatById, + getChatAccessGrants, + updateChatAccessGrants + } from '$lib/apis/chats'; import { copyToClipboard } from '$lib/utils'; import Modal from '../common/Modal.svelte'; import Link from '../icons/Link.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; + import AccessControl from '$lib/components/workspace/common/AccessControl.svelte'; export let chatId; let chat = null; let shareUrl = null; + let accessGrants: any[] = []; const i18n = getContext('i18n'); const shareLocalChat = async () => { const _chat = chat; const sharedChat = await shareChatById(localStorage.token, chatId); - shareUrl = `${window.location.origin}/s/${sharedChat.id}`; + shareUrl = `${window.location.origin}/s/${sharedChat.share_id}`; console.log(shareUrl); chat = await getChatById(localStorage.token, chatId); @@ -54,6 +62,25 @@ ); }; + const loadAccessGrants = async () => { + if (!chatId) return; + try { + accessGrants = (await getChatAccessGrants(localStorage.token, chatId)) ?? []; + } catch (e) { + console.error('Failed to load access grants', e); + accessGrants = []; + } + }; + + const saveAccessGrants = async () => { + try { + await updateChatAccessGrants(localStorage.token, chatId, accessGrants); + toast.success($i18n.t('Access updated')); + } catch (e) { + toast.error(`${e}`); + } + }; + export let show = false; const isDifferentChat = (_chat) => { @@ -73,8 +100,10 @@ if (isDifferentChat(_chat)) { chat = _chat; } + await loadAccessGrants(); } else { chat = null; + accessGrants = []; console.log(chat); } })(); @@ -97,8 +126,8 @@
{#if chat} -
-
+
+ -
-
-
- {#if $config?.features.enable_community_sharing} - - {/if} - - -
+ {#if chat.share_id} +
+
+ {/if} + +
+ {#if $config?.features.enable_community_sharing} + + {/if} + +
{/if} From bd358091056cc9dd4203028a25342949cc148a7d Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 10:22:43 +0900 Subject: [PATCH 012/119] refac --- .../chat/FileNav/FileEntryRow.svelte | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/components/chat/FileNav/FileEntryRow.svelte b/src/lib/components/chat/FileNav/FileEntryRow.svelte index 93baf941fa..ef56c2c6b5 100644 --- a/src/lib/components/chat/FileNav/FileEntryRow.svelte +++ b/src/lib/components/chat/FileNav/FileEntryRow.svelte @@ -1,4 +1,5 @@
@@ -77,10 +68,12 @@ { - builtinTools = { - ...builtinTools, - [tool]: e.detail === 'checked' - }; + if (e.detail === 'checked') { + delete builtinTools[tool]; + } else { + builtinTools[tool] = false; + } + builtinTools = builtinTools; }} /> From e5f31c2e14058228769158d2a1f654e3cecdb05a Mon Sep 17 00:00:00 2001 From: Algorithm5838 <108630393+Algorithm5838@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:22:06 +0300 Subject: [PATCH 046/119] perf: replace JSON.stringify equality with fast-deep-equal (#23370) --- package-lock.json | 2 +- package.json | 1 + .../components/chat/Messages/MultiResponseMessages.svelte | 3 ++- src/lib/components/chat/Messages/ResponseMessage.svelte | 3 ++- .../chat/Messages/ResponseMessage/StatusHistory.svelte | 6 ++---- src/lib/components/chat/Messages/UserMessage.svelte | 3 ++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 538792470e..e917faa608 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "dayjs": "^1.11.10", "dompurify": "^3.2.6", "eventsource-parser": "^1.1.2", + "fast-deep-equal": "^3.1.3", "file-saver": "^2.0.5", "focus-trap": "^7.6.4", "fuse.js": "^7.0.0", @@ -8462,7 +8463,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-fifo": { diff --git a/package.json b/package.json index ab0cf78afc..204f35d51f 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "dayjs": "^1.11.10", "dompurify": "^3.2.6", "eventsource-parser": "^1.1.2", + "fast-deep-equal": "^3.1.3", "file-saver": "^2.0.5", "focus-trap": "^7.6.4", "fuse.js": "^7.0.0", diff --git a/src/lib/components/chat/Messages/MultiResponseMessages.svelte b/src/lib/components/chat/Messages/MultiResponseMessages.svelte index ae2be93ff2..a6d5a9b8a7 100644 --- a/src/lib/components/chat/Messages/MultiResponseMessages.svelte +++ b/src/lib/components/chat/Messages/MultiResponseMessages.svelte @@ -19,6 +19,7 @@ import localizedFormat from 'dayjs/plugin/localizedFormat'; import ProfileImage from './ProfileImage.svelte'; import { WEBUI_BASE_URL } from '$lib/constants'; + import equal from 'fast-deep-equal'; const i18n = getContext('i18n'); dayjs.extend(localizedFormat); @@ -66,7 +67,7 @@ if (source) { if (message.content !== source.content || message.done !== source.done) { message = structuredClone(source); - } else if (JSON.stringify(message) !== JSON.stringify(source)) { + } else if (!equal(message, source)) { message = structuredClone(source); } } diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 35e592d139..ebae97d95b 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -37,6 +37,7 @@ removeAllDetails } from '$lib/utils'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; + import equal from 'fast-deep-equal'; import Name from './Name.svelte'; import ProfileImage from './ProfileImage.svelte'; @@ -127,7 +128,7 @@ // Avoids 2x O(n) JSON.stringify calls that are always true during streaming anyway if (message.content !== source.content || message.done !== source.done) { message = structuredClone(source); - } else if (JSON.stringify(message) !== JSON.stringify(source)) { + } else if (!equal(message, source)) { // Slow path: full comparison for infrequent changes (sources, annotations, status, etc.) message = structuredClone(source); } diff --git a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte index 5aaf774694..bb65bf331b 100644 --- a/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage/StatusHistory.svelte @@ -3,6 +3,7 @@ const i18n = getContext('i18n'); import StatusItem from './StatusHistory/StatusItem.svelte'; + import equal from 'fast-deep-equal'; export let statusHistory = []; export let expand = false; @@ -21,10 +22,7 @@ status = history.at(-1); } - $: if ( - statusHistory.length !== history.length || - JSON.stringify(statusHistory) !== JSON.stringify(history) - ) { + $: if (!equal(statusHistory, history)) { history = statusHistory; } diff --git a/src/lib/components/chat/Messages/UserMessage.svelte b/src/lib/components/chat/Messages/UserMessage.svelte index ec48a2e1ff..3b26dba1ff 100644 --- a/src/lib/components/chat/Messages/UserMessage.svelte +++ b/src/lib/components/chat/Messages/UserMessage.svelte @@ -7,6 +7,7 @@ import { user as _user } from '$lib/stores'; import { copyToClipboard as _copyToClipboard, formatDate } from '$lib/utils'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; + import equal from 'fast-deep-equal'; import Name from './Name.svelte'; import ProfileImage from './ProfileImage.svelte'; @@ -58,7 +59,7 @@ if (source) { if (message.content !== source.content) { message = structuredClone(source); - } else if (JSON.stringify(message) !== JSON.stringify(source)) { + } else if (!equal(message, source)) { message = structuredClone(source); } } From e8e655d0de1c39d0131c0297cf5ac41870a7cfd9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 14:24:34 +0900 Subject: [PATCH 047/119] chore: dep bump --- package-lock.json | 205 +++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index e917faa608..3e52640600 100644 --- a/package-lock.json +++ b/package-lock.json @@ -215,42 +215,40 @@ "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", - "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", + "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.1.2", - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" + "@chevrotain/gast": "12.0.0", + "@chevrotain/types": "12.0.0" } }, "node_modules/@chevrotain/gast": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", - "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz", + "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.1.2", - "lodash-es": "4.17.23" + "@chevrotain/types": "12.0.0" } }, "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", - "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", + "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", - "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz", + "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", - "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz", + "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", "license": "Apache-2.0" }, "node_modules/@codemirror/autocomplete": { @@ -1248,9 +1246,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -1374,9 +1372,9 @@ "license": "MIT" }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -3584,9 +3582,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.55.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz", - "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==", + "version": "2.57.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.57.1.tgz", + "integrity": "sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -3612,7 +3610,7 @@ "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3", + "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -5390,9 +5388,9 @@ "license": "MIT" }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5620,9 +5618,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -5964,9 +5962,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6369,29 +6367,31 @@ } }, "node_modules/chevrotain": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", - "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz", + "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.1.2", - "@chevrotain/gast": "11.1.2", - "@chevrotain/regexp-to-ast": "11.1.2", - "@chevrotain/types": "11.1.2", - "@chevrotain/utils": "11.1.2", - "lodash-es": "4.17.23" + "@chevrotain/cst-dts-gen": "12.0.0", + "@chevrotain/gast": "12.0.0", + "@chevrotain/regexp-to-ast": "12.0.0", + "@chevrotain/types": "12.0.0", + "@chevrotain/utils": "12.0.0" + }, + "engines": { + "node": ">=22.0.0" } }, "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz", + "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==", "license": "MIT", "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { - "chevrotain": "^11.0.0" + "chevrotain": "^12.0.0" } }, "node_modules/chokidar": { @@ -7744,9 +7744,9 @@ } }, "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.0.tgz", + "integrity": "sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -8239,9 +8239,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9051,9 +9051,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -10152,13 +10152,14 @@ } }, "node_modules/langium": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", - "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz", + "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.1.1", - "chevrotain-allstar": "~0.3.1", + "@chevrotain/regexp-to-ast": "~12.0.0", + "chevrotain": "~12.0.0", + "chevrotain-allstar": "~0.4.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" @@ -10600,16 +10601,16 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -10870,9 +10871,9 @@ "license": "MIT" }, "node_modules/matcher-collection/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -11103,9 +11104,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -11765,9 +11766,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -11914,9 +11915,9 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", "engines": { @@ -12326,9 +12327,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -12460,9 +12461,9 @@ "license": "MIT" }, "node_modules/quick-temp/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -12742,9 +12743,9 @@ "license": "MIT" }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -15408,9 +15409,9 @@ } }, "node_modules/vite-plugin-static-copy/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -16152,9 +16153,9 @@ "license": "MIT" }, "node_modules/walk-sync/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -16441,9 +16442,9 @@ } }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" From 4113b15a60771a8945319ef48e1c3c9fac5f3645 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 17 Apr 2026 14:28:18 +0900 Subject: [PATCH 048/119] chore: format --- .github/pull_request_template.md | 1 - .../c1d2e3f4a5b6_add_shared_chat_table.py | 74 +++++++++---------- backend/open_webui/models/chats.py | 4 +- backend/open_webui/models/prompts.py | 8 +- backend/open_webui/models/shared_chats.py | 29 ++------ backend/open_webui/retrieval/utils.py | 2 +- backend/open_webui/routers/memories.py | 11 +-- backend/open_webui/routers/retrieval.py | 1 - .../utils/access_control/__init__.py | 5 +- backend/open_webui/utils/middleware.py | 61 ++++++++++++--- backend/open_webui/utils/models.py | 1 - package-lock.json | 4 +- package.json | 2 +- src/lib/apis/chats/index.ts | 6 +- src/lib/components/chat/MessageInput.svelte | 21 ++++-- src/lib/components/chat/ShareChatModal.svelte | 6 +- .../workspace/Models/ModelEditor.svelte | 8 +- src/lib/i18n/locales/ar-BH/translation.json | 6 ++ src/lib/i18n/locales/ar/translation.json | 6 ++ src/lib/i18n/locales/az-AZ/translation.json | 6 ++ src/lib/i18n/locales/bg-BG/translation.json | 6 ++ src/lib/i18n/locales/bn-BD/translation.json | 6 ++ src/lib/i18n/locales/bo-TB/translation.json | 6 ++ src/lib/i18n/locales/bs-BA/translation.json | 6 ++ src/lib/i18n/locales/ca-ES/translation.json | 6 ++ src/lib/i18n/locales/ceb-PH/translation.json | 6 ++ src/lib/i18n/locales/cs-CZ/translation.json | 6 ++ src/lib/i18n/locales/da-DK/translation.json | 6 ++ src/lib/i18n/locales/de-DE/translation.json | 6 ++ src/lib/i18n/locales/dg-DG/translation.json | 6 ++ src/lib/i18n/locales/el-GR/translation.json | 6 ++ src/lib/i18n/locales/en-GB/translation.json | 6 ++ src/lib/i18n/locales/en-US/translation.json | 6 ++ src/lib/i18n/locales/es-ES/translation.json | 6 ++ src/lib/i18n/locales/et-EE/translation.json | 6 ++ src/lib/i18n/locales/eu-ES/translation.json | 6 ++ src/lib/i18n/locales/fa-IR/translation.json | 6 ++ src/lib/i18n/locales/fi-FI/translation.json | 6 ++ src/lib/i18n/locales/fr-CA/translation.json | 6 ++ src/lib/i18n/locales/fr-FR/translation.json | 6 ++ src/lib/i18n/locales/gl-ES/translation.json | 6 ++ src/lib/i18n/locales/he-IL/translation.json | 6 ++ src/lib/i18n/locales/hi-IN/translation.json | 6 ++ src/lib/i18n/locales/hr-HR/translation.json | 6 ++ src/lib/i18n/locales/hu-HU/translation.json | 6 ++ src/lib/i18n/locales/id-ID/translation.json | 6 ++ src/lib/i18n/locales/ie-GA/translation.json | 8 ++ src/lib/i18n/locales/it-IT/translation.json | 6 ++ src/lib/i18n/locales/ja-JP/translation.json | 6 ++ src/lib/i18n/locales/ka-GE/translation.json | 6 ++ src/lib/i18n/locales/kab-DZ/translation.json | 6 ++ src/lib/i18n/locales/ko-KR/translation.json | 6 ++ src/lib/i18n/locales/lt-LT/translation.json | 6 ++ src/lib/i18n/locales/lv-LV/translation.json | 6 ++ src/lib/i18n/locales/ms-MY/translation.json | 6 ++ src/lib/i18n/locales/nb-NO/translation.json | 6 ++ src/lib/i18n/locales/nl-NL/translation.json | 6 ++ src/lib/i18n/locales/pa-IN/translation.json | 6 ++ src/lib/i18n/locales/pl-PL/translation.json | 6 ++ src/lib/i18n/locales/pt-BR/translation.json | 6 ++ src/lib/i18n/locales/pt-PT/translation.json | 6 ++ src/lib/i18n/locales/ro-RO/translation.json | 6 ++ src/lib/i18n/locales/ru-RU/translation.json | 6 ++ src/lib/i18n/locales/sk-SK/translation.json | 6 ++ src/lib/i18n/locales/sr-RS/translation.json | 6 ++ src/lib/i18n/locales/sv-SE/translation.json | 6 ++ src/lib/i18n/locales/ta-IN/translation.json | 6 ++ src/lib/i18n/locales/th-TH/translation.json | 6 ++ src/lib/i18n/locales/tk-TM/translation.json | 6 ++ src/lib/i18n/locales/tr-TR/translation.json | 6 ++ src/lib/i18n/locales/ug-CN/translation.json | 6 ++ src/lib/i18n/locales/uk-UA/translation.json | 6 ++ src/lib/i18n/locales/ur-PK/translation.json | 6 ++ .../i18n/locales/uz-Cyrl-UZ/translation.json | 6 ++ .../i18n/locales/uz-Latn-Uz/translation.json | 6 ++ src/lib/i18n/locales/vi-VN/translation.json | 6 ++ src/lib/i18n/locales/zh-CN/translation.json | 6 ++ src/lib/i18n/locales/zh-TW/translation.json | 6 ++ src/lib/utils/index.ts | 6 +- 79 files changed, 501 insertions(+), 117 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2dfe189f0d..ad311a371a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,7 +16,6 @@ This is to ensure large feature PRs are discussed with the community first, befo The most impactful way to contribute to Open WebUI is through well-written bug reports, detailed feature discussions, and thoughtful ideas. These directly shape the project. If you do open a pull request, please know that Open WebUI is held to the highest standard of code quality, consistency, and architectural coherence, and every line merged becomes something the core team must own, maintain, and support indefinitely. Submitted code may be refactored, rewritten, or used as inspiration for a different implementation. This is not a reflection of your work's quality. It is how we ensure that a small team can deeply understand and evolve every part of the codebase. --> - **Before submitting, make sure you've checked the following:** - [ ] **Target branch:** Verify that the pull request targets the `dev` branch. **PRs targeting `main` will be immediately closed.** diff --git a/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py b/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py index ca2f9e7cd3..2451f50ae2 100644 --- a/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py +++ b/backend/open_webui/migrations/versions/c1d2e3f4a5b6_add_shared_chat_table.py @@ -91,41 +91,41 @@ def upgrade(): original_chat_id = row.user_id.replace('shared-', '', 1) # Verify original chat still exists - original = conn.execute( - sa.select(chat_t.c.user_id).where(chat_t.c.id == original_chat_id) - ).fetchone() + original = conn.execute(sa.select(chat_t.c.user_id).where(chat_t.c.id == original_chat_id)).fetchone() if not original: continue # Insert snapshot into shared_chat - conn.execute(shared_chat_t.insert().values( - id=share_token, - chat_id=original_chat_id, - user_id=original.user_id, - title=row.title, - chat=row.chat, - created_at=row.created_at, - updated_at=row.updated_at, - )) + conn.execute( + shared_chat_t.insert().values( + id=share_token, + chat_id=original_chat_id, + user_id=original.user_id, + title=row.title, + chat=row.chat, + created_at=row.created_at, + updated_at=row.updated_at, + ) + ) # Create user:*:read grant for backward compat - conn.execute(access_grant_t.insert().values( - id=str(uuid.uuid4()), - resource_type='shared_chat', - resource_id=original_chat_id, - principal_type='user', - principal_id='*', - permission='read', - created_at=row.created_at or int(time.time()), - )) + conn.execute( + access_grant_t.insert().values( + id=str(uuid.uuid4()), + resource_type='shared_chat', + resource_id=original_chat_id, + principal_type='user', + principal_id='*', + permission='read', + created_at=row.created_at or int(time.time()), + ) + ) # 3. Clean up old phantom rows conn.execute( chat_message_t.delete().where( - chat_message_t.c.chat_id.in_( - sa.select(chat_t.c.id).where(chat_t.c.user_id.like('shared-%')) - ) + chat_message_t.c.chat_id.in_(sa.select(chat_t.c.id).where(chat_t.c.user_id.like('shared-%'))) ) ) conn.execute(chat_t.delete().where(chat_t.c.user_id.like('shared-%'))) @@ -147,18 +147,18 @@ def downgrade(): ).fetchall() for row in shared_rows: - conn.execute(chat_t.insert().values( - id=row.id, - user_id=f'shared-{row.chat_id}', - title=row.title, - chat=row.chat, - created_at=row.created_at, - updated_at=row.updated_at, - archived=False, - meta={}, - )) + conn.execute( + chat_t.insert().values( + id=row.id, + user_id=f'shared-{row.chat_id}', + title=row.title, + chat=row.chat, + created_at=row.created_at, + updated_at=row.updated_at, + archived=False, + meta={}, + ) + ) - conn.execute( - access_grant_t.delete().where(access_grant_t.c.resource_type == 'shared_chat') - ) + conn.execute(access_grant_t.delete().where(access_grant_t.c.resource_type == 'shared_chat')) op.drop_table('shared_chat') diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index be65a92c91..ba6611a811 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -723,9 +723,7 @@ class ChatTable: """Delegate to SharedChats for listing shared chats by user.""" from open_webui.models.shared_chats import SharedChats - return await SharedChats.get_by_user_id( - user_id, filter=filter, skip=skip, limit=limit, db=db - ) + return await SharedChats.get_by_user_id(user_id, filter=filter, skip=skip, limit=limit, db=db) async def get_chat_list_by_user_id( self, diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 808073a458..dec1848aa7 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -299,15 +299,17 @@ class PromptsTable: if dialect_name == 'sqlite': tag_clause = text( - "EXISTS (SELECT 1 FROM json_each(prompt.tags) t WHERE LOWER(t.value) = :tag_val)" + 'EXISTS (SELECT 1 FROM json_each(prompt.tags) t WHERE LOWER(t.value) = :tag_val)' ) elif dialect_name == 'postgresql': tag_clause = text( - "EXISTS (SELECT 1 FROM json_array_elements_text(prompt.tags) t WHERE LOWER(t) = :tag_val)" + 'EXISTS (SELECT 1 FROM json_array_elements_text(prompt.tags) t WHERE LOWER(t) = :tag_val)' ) else: # Fallback: LIKE on serialised JSON text (ASCII-safe only) - tag_clause = func.lower(cast(Prompt.tags, String)).like(f'%{json.dumps(tag_lower, ensure_ascii=False)}%') + tag_clause = func.lower(cast(Prompt.tags, String)).like( + f'%{json.dumps(tag_lower, ensure_ascii=False)}%' + ) tag_lower = None if tag_lower is not None: diff --git a/backend/open_webui/models/shared_chats.py b/backend/open_webui/models/shared_chats.py index 1a042922fb..37a3fea852 100644 --- a/backend/open_webui/models/shared_chats.py +++ b/backend/open_webui/models/shared_chats.py @@ -60,9 +60,7 @@ class SharedChatResponse(BaseModel): class SharedChatsTable: - async def create( - self, chat_id: str, user_id: str, db: Optional[AsyncSession] = None - ) -> Optional[SharedChatModel]: + async def create(self, chat_id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[SharedChatModel]: """ Create a snapshot of the chat for link sharing. Returns the SharedChatModel with the share token as its id. @@ -92,9 +90,7 @@ class SharedChatsTable: return SharedChatModel.model_validate(shared_chat) - async def update( - self, share_id: str, db: Optional[AsyncSession] = None - ) -> Optional[SharedChatModel]: + async def update(self, share_id: str, db: Optional[AsyncSession] = None) -> Optional[SharedChatModel]: """ Re-snapshot: update the shared chat with the current state of the original chat. """ @@ -117,9 +113,7 @@ class SharedChatsTable: await db.refresh(shared_chat) return SharedChatModel.model_validate(shared_chat) - async def get_by_id( - self, share_id: str, db: Optional[AsyncSession] = None - ) -> Optional[SharedChatModel]: + async def get_by_id(self, share_id: str, db: Optional[AsyncSession] = None) -> Optional[SharedChatModel]: """Get a shared chat by its share token.""" async with get_async_db_context(db) as db: shared_chat = await db.get(SharedChat, share_id) @@ -127,16 +121,11 @@ class SharedChatsTable: return SharedChatModel.model_validate(shared_chat) return None - async def get_by_chat_id( - self, chat_id: str, db: Optional[AsyncSession] = None - ) -> Optional[SharedChatModel]: + async def get_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> Optional[SharedChatModel]: """Get the shared chat for a given original chat. Returns the most recent one.""" async with get_async_db_context(db) as db: result = await db.execute( - select(SharedChat) - .filter_by(chat_id=chat_id) - .order_by(SharedChat.updated_at.desc()) - .limit(1) + select(SharedChat).filter_by(chat_id=chat_id).order_by(SharedChat.updated_at.desc()).limit(1) ) shared_chat = result.scalars().first() if shared_chat: @@ -194,9 +183,7 @@ class SharedChatsTable: for sc in result.scalars().all() ] - async def delete_by_id( - self, share_id: str, db: Optional[AsyncSession] = None - ) -> bool: + async def delete_by_id(self, share_id: str, db: Optional[AsyncSession] = None) -> bool: """Delete a shared chat by its share token.""" try: async with get_async_db_context(db) as db: @@ -206,9 +193,7 @@ class SharedChatsTable: except Exception: return False - async def delete_by_chat_id( - self, chat_id: str, db: Optional[AsyncSession] = None - ) -> bool: + async def delete_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> bool: """Delete all shared chats for a given original chat.""" try: async with get_async_db_context(db) as db: diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 07c1ae5210..93ba72ce13 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -959,7 +959,7 @@ async def filter_accessible_collections( # System meta-collection — never exposed to non-admins. continue elif name.startswith('file-'): - file_id = name[len('file-'):] + file_id = name[len('file-') :] if await has_access_to_file(file_id=file_id, access_type=access_type, user=user): validated.add(name) elif name.startswith('user-memory-'): diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py index b275c84e29..6522118258 100644 --- a/backend/open_webui/routers/memories.py +++ b/backend/open_webui/routers/memories.py @@ -150,15 +150,8 @@ async def query_memory( # same RELEVANCE_THRESHOLD used by RAG ensures only genuinely matching # memories are surfaced (distances are normalised to 0→1, higher is # better). - relevance_threshold = getattr( - request.app.state.config, 'RELEVANCE_THRESHOLD', 0.0 - ) - if ( - results - and relevance_threshold > 0.0 - and results.distances - and results.distances[0] - ): + relevance_threshold = getattr(request.app.state.config, 'RELEVANCE_THRESHOLD', 0.0) + if results and relevance_threshold > 0.0 and results.distances and results.distances[0]: from open_webui.retrieval.vector.main import SearchResult filtered_ids = [] diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 85686c4fcf..ee8a9007fe 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -2365,7 +2365,6 @@ async def _validate_collection_access(collection_names: list[str], user, access_ ) - class QueryDocForm(BaseModel): collection_name: str query: str diff --git a/backend/open_webui/utils/access_control/__init__.py b/backend/open_webui/utils/access_control/__init__.py index 7eba61770e..06e8d0aba0 100644 --- a/backend/open_webui/utils/access_control/__init__.py +++ b/backend/open_webui/utils/access_control/__init__.py @@ -339,11 +339,8 @@ async def check_model_access( raise HTTPException(status_code=403, detail='Model not found') # Enforce access on chained base models - if not await has_base_model_access( - user.id, model_info, user_group_ids=user_group_ids - ): + if not await has_base_model_access(user.id, model_info, user_group_ids=user_group_ids): raise HTTPException(status_code=403, detail='Model not found') else: if user.role != 'admin': raise HTTPException(status_code=403, detail='Model not found') - diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 2ed08fedab..404e36cfa3 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -452,6 +452,50 @@ def serialize_output(output: list) -> str: # Already handled inline with function_call above pass + elif item_type in ('web_search_call', 'file_search_call', 'computer_call'): + # OpenAI Responses API built-in server-side tool output items. + # These are emitted when the model uses native tools (web_search, + # file_search, computer_use) through the Responses API. Render as + # collapsible tool call blocks matching the function_call pattern. + if content and not content.endswith('\n'): + content += '\n' + + call_id = item.get('id', '') + status = item.get('status', 'in_progress') + + # Derive a human-readable display name + display_names = { + 'web_search_call': 'Web Search', + 'file_search_call': 'File Search', + 'computer_call': 'Computer Use', + } + display_name = display_names.get(item_type, item_type) + + # Extract a summary of what the tool did for the details body + summary_text = '' + if item_type == 'web_search_call': + action = item.get('action', {}) + if isinstance(action, dict): + query = action.get('query', '') + if query: + summary_text = f'Query: {query}' + elif item_type == 'file_search_call': + queries = item.get('queries', []) + if queries: + summary_text = f'Queries: {", ".join(str(q) for q in queries)}' + elif item_type == 'computer_call': + action = item.get('action', {}) + if isinstance(action, dict): + action_type = action.get('type', '') + if action_type: + summary_text = f'Action: {action_type}' + + done = status == 'completed' or idx != len(output) - 1 + if done: + content += f'
\nTool Executed\n{html.escape(summary_text)}\n
\n' + else: + content += f'
\nExecuting...\n
\n' + elif item_type == 'reasoning': reasoning_content = '' # Check for 'summary' (new structure) or 'content' (legacy/fallback) @@ -2619,9 +2663,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Resolve terminal tools if terminal_id is set (outside tool_ids check # so system terminals work even when no other tools are selected) - terminal_capability = (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get( - 'terminal', True - ) + terminal_capability = (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get('terminal', True) if terminal_id and terminal_capability: try: terminal_result = await get_terminal_tools( @@ -3110,11 +3152,13 @@ async def outlet_filter_handler(ctx): # Append the full assistant message (content, output, usage, etc.) if assistant_message: - message_list.append({ - 'id': message_id, - 'role': 'assistant', - **assistant_message, - }) + message_list.append( + { + 'id': message_id, + 'role': 'assistant', + **assistant_message, + } + ) else: messages_map = await Chats.get_messages_map_by_chat_id(chat_id) if not messages_map: @@ -4221,7 +4265,6 @@ async def streaming_chat_response_handler(response, ctx): ) reasoning_item['status'] = 'completed' - if response_tool_calls: tool_calls.append(_split_tool_calls(response_tool_calls)) diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 33642a339d..6b12515ba1 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -413,7 +413,6 @@ async def check_model_access(user, model, db=None): raise Exception('Model not found') - async def get_filtered_models(models, user, db=None): # Filter out models that the user does not have access to if ( diff --git a/package-lock.json b/package-lock.json index 3e52640600..8efa79c1b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.8.12", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.8.12", + "version": "0.9.0", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index 204f35d51f..bc1a1c5da3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.8.12", + "version": "0.9.0", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 751fc823f2..028371386c 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -953,11 +953,7 @@ export const deleteSharedChatById = async (token: string, id: string) => { return res; }; -export const updateChatAccessGrants = async ( - token: string, - id: string, - accessGrants: object[] -) => { +export const updateChatAccessGrants = async (token: string, id: string, accessGrants: object[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/shared/${id}/access/update`, { diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 041c511493..92de0c3d43 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -496,11 +496,8 @@ ); let terminalCapableModels = []; - $: terminalCapableModels = ( - atSelectedModel?.id ? [atSelectedModel.id] : selectedModels - ).filter( - (model) => - $models.find((m) => m.id === model)?.info?.meta?.capabilities?.terminal ?? true + $: terminalCapableModels = (atSelectedModel?.id ? [atSelectedModel.id] : selectedModels).filter( + (model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.terminal ?? true ); let toggleFilters = []; @@ -1760,12 +1757,18 @@ + diff --git a/src/lib/components/calendar/CalendarEventModal.svelte b/src/lib/components/calendar/CalendarEventModal.svelte new file mode 100644 index 0000000000..6e334e9cc6 --- /dev/null +++ b/src/lib/components/calendar/CalendarEventModal.svelte @@ -0,0 +1,260 @@ + + + +
+ +
+ + +
+ + +
+ +
+
{$i18n.t('Calendar')}
+ +
+ + +
+
{$i18n.t('When')}
+
+ + {#if !allDay} + + + + {/if} + +
+
+ + +
+
{$i18n.t('Location')}
+ +
+ + +
+
{$i18n.t('Description')}
+ +
+
+ + +
+
+ {#if event && !event.meta?.automation_id} + + {/if} +
+ +
+ + +
+
+
+
diff --git a/src/lib/components/calendar/CalendarSidebar.svelte b/src/lib/components/calendar/CalendarSidebar.svelte new file mode 100644 index 0000000000..09e604a281 --- /dev/null +++ b/src/lib/components/calendar/CalendarSidebar.svelte @@ -0,0 +1,174 @@ + + +
+ +
+
+
{miniMonthNames[miniMonth]} {miniYear}
+
+ + +
+
+ +
+ {#each ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as d} +
{d}
+ {/each} +
+ +
+ {#each miniDays as day} + + {/each} +
+
+ + +
+
+
+ {$i18n.t('Calendars')} +
+
+ + {#each calendars as cal (cal.id)} + + {/each} +
+
diff --git a/src/lib/components/calendar/CalendarView.svelte b/src/lib/components/calendar/CalendarView.svelte new file mode 100644 index 0000000000..0b67d7de98 --- /dev/null +++ b/src/lib/components/calendar/CalendarView.svelte @@ -0,0 +1,380 @@ + + +
+ + + + + {#if view === 'month'} +
+
+ {#each DAY_NAMES as day} +
{$i18n.t(day)}
+ {/each} +
+ +
+ {#each monthDays as day, i} + {@const dayKey = new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime().toString()} + {@const dayEvents = eventsByDay[dayKey] || []} + {@const col = i % 7} + {@const row = Math.floor(i / 7)} + + {/each} +
+
+ + + {:else if view === 'week'} +
+
+
+
+
+
+ {#each weekDays as day} +
+
{DAY_NAMES[day.getDay()]}
+
+ {day.getDate()} +
+
+ {/each} +
+ +
+ {#each hours as hour} +
+
{hour > 0 ? formatHour(hour) : ''}
+ {#each weekDays as day} + {@const hourEvents = getEventsForHour(day, hour, filteredEvents)} + + {/each} +
+ {/each} +
+
+
+
+
+ + + {:else} +
+
+ {#each hours as hour} + {@const hourEvents = getEventsForHour(currentDate, hour, filteredEvents)} +
+
{hour > 0 ? formatHour(hour) : ''}
+ +
+ {/each} +
+
+ {/if} +
diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index 1a1681a844..30c29962b4 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -250,6 +250,38 @@
{/if} + {#if $user?.role === 'admin' || $user?.permissions?.features?.calendar} + { + if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return; + e.preventDefault(); + show = false; + goto('/calendar'); + }} + > +
+ + + +
+
{$i18n.t('Calendar')}
+
+ {/if} + {#if role === 'admin'} + import { onMount, getContext, tick } from 'svelte'; + import { toast } from 'svelte-sonner'; + import { goto } from '$app/navigation'; + import { WEBUI_NAME, mobile, showSidebar, user } from '$lib/stores'; + import { + getCalendars, + getCalendarEvents, + type CalendarModel, + type CalendarEventModel + } from '$lib/apis/calendar'; + import CalendarView from '$lib/components/calendar/CalendarView.svelte'; + import CalendarSidebar from '$lib/components/calendar/CalendarSidebar.svelte'; + import CalendarEventModal from '$lib/components/calendar/CalendarEventModal.svelte'; + import Spinner from '$lib/components/common/Spinner.svelte'; + import Plus from '$lib/components/icons/Plus.svelte'; + + const i18n = getContext('i18n'); + + let loaded = false; + let calendars: CalendarModel[] = []; + let events: CalendarEventModel[] = []; + let visibleCalendarIds: Set = new Set(); + + let view: 'month' | 'week' | 'day' = 'month'; + let currentDate = new Date(); + + let showEventModal = false; + let editEvent: CalendarEventModel | null = null; + let defaultStartAt: number | null = null; + + function getVisibleRange(): { start: string; end: string } { + const d = new Date(currentDate); + let start: Date; + let end: Date; + + if (view === 'month') { + start = new Date(d.getFullYear(), d.getMonth(), 1); + start.setDate(start.getDate() - start.getDay()); + end = new Date(start); + end.setDate(end.getDate() + 42); + } else if (view === 'week') { + start = new Date(d); + start.setDate(start.getDate() - start.getDay()); + start.setHours(0, 0, 0, 0); + end = new Date(start); + end.setDate(end.getDate() + 7); + } else { + start = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + end = new Date(start); + end.setDate(end.getDate() + 1); + } + + return { + start: start.toISOString(), + end: end.toISOString() + }; + } + + async function loadCalendars() { + try { + calendars = (await getCalendars(localStorage.token)) ?? []; + visibleCalendarIds = new Set(calendars.map((c) => c.id)); + } catch (err) { + console.error('loadCalendars', err); + calendars = []; + } + } + + async function loadEvents() { + try { + const { start, end } = getVisibleRange(); + events = await getCalendarEvents(localStorage.token, start, end); + } catch (err) { + toast.error(`${err}`); + } + } + + async function refresh() { + await loadEvents(); + } + + function toggleCalendar(id: string) { + const next = new Set(visibleCalendarIds); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + visibleCalendarIds = next; + } + + function handleCreateEvent(e: CustomEvent<{ start_at: number }>) { + editEvent = null; + defaultStartAt = e.detail.start_at; + showEventModal = true; + } + + function handleEventClick(e: CustomEvent) { + const evt = e.detail; + if (evt.meta?.automation_id) { + if (evt.meta?.chat_id) { + goto(`/c/${evt.meta.chat_id}`); + } else { + goto(`/automations/${evt.meta.automation_id}`); + } + return; + } + editEvent = evt; + defaultStartAt = null; + showEventModal = true; + } + + async function handleNavigate() { + await tick(); + refresh(); + } + + async function handleDateSelect(date: Date) { + currentDate = date; + await tick(); + refresh(); + } + + function handleNewEvent() { + editEvent = null; + defaultStartAt = null; + showEventModal = true; + } + + $: defaultCalendarId = calendars.find((c) => !c.is_system || c.name === 'Personal')?.id || calendars[0]?.id || ''; + + onMount(async () => { + await loadCalendars(); + await refresh(); + loaded = true; + }); + + + + {$i18n.t('Calendar')} • {$WEBUI_NAME} + + + refresh()} + on:delete={() => refresh()} +/> + +
+ {#if loaded} +
+ + + + +
+ +
+
+ {:else} +
+ +
+ {/if} +
From 4a5401b4174edbef8d102ac2917944ae1d2cdc00 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 21:48:27 +0900 Subject: [PATCH 057/119] refac --- .../56359461a091_add_calendar_tables.py | 2 +- backend/open_webui/models/calendar.py | 40 ++++++++++++++----- backend/open_webui/routers/calendar.py | 10 ++++- src/lib/apis/calendar/index.ts | 33 ++++++++++++++- src/routes/(app)/calendar/+page.svelte | 2 +- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py index 8277daa738..a0812578c8 100644 --- a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py +++ b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py @@ -24,7 +24,7 @@ def upgrade() -> None: sa.Column('user_id', sa.Text(), nullable=False), sa.Column('name', sa.Text(), nullable=False), sa.Column('color', sa.Text(), nullable=True), - sa.Column('is_system', sa.Boolean(), nullable=False), + sa.Column('is_default', sa.Boolean(), nullable=False), sa.Column('data', sa.JSON(), nullable=True), sa.Column('meta', sa.JSON(), nullable=True), sa.Column('created_at', sa.BigInteger(), nullable=False), diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index e055c87f90..859632c494 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -17,6 +17,7 @@ from sqlalchemy import ( exists, func, delete, + update, ) from sqlalchemy.ext.asyncio import AsyncSession @@ -40,7 +41,7 @@ class Calendar(Base): user_id = Column(Text, nullable=False) name = Column(Text, nullable=False) color = Column(Text, nullable=True) - is_system = Column(Boolean, nullable=False, default=False) + is_default = Column(Boolean, nullable=False, default=False) data = Column(JSON, nullable=True) meta = Column(JSON, nullable=True) @@ -107,7 +108,8 @@ class CalendarModel(BaseModel): user_id: str name: str color: Optional[str] = None - is_system: bool = False + is_default: bool = False + data: Optional[dict] = None meta: Optional[dict] = None @@ -269,7 +271,7 @@ class CalendarTable: user_id=user_id, name='Personal', color='#3b82f6', - is_system=True, + is_default=True, created_at=now, updated_at=now, ), @@ -278,7 +280,6 @@ class CalendarTable: user_id=user_id, name='Scheduled Tasks', color='#8b5cf6', - is_system=True, created_at=now + 1, updated_at=now + 1, ), @@ -338,7 +339,6 @@ class CalendarTable: select(Calendar).filter( Calendar.user_id == user_id, Calendar.name == 'Scheduled Tasks', - Calendar.is_system == True, ) ) cal = result.scalars().first() @@ -349,7 +349,6 @@ class CalendarTable: select(Calendar).filter( Calendar.user_id == user_id, Calendar.name == 'Scheduled Tasks', - Calendar.is_system == True, ) ) cal = result.scalars().first() @@ -366,7 +365,7 @@ class CalendarTable: user_id=user_id, name=form_data.name, color=form_data.color, - is_system=False, + is_default=False, data=form_data.data, meta=form_data.meta, created_at=now, @@ -403,13 +402,36 @@ class CalendarTable: await db.commit() return await self._to_calendar_model(cal, db=db) + async def set_default_calendar( + self, user_id: str, calendar_id: str, db: Optional[AsyncSession] = None + ) -> Optional[CalendarModel]: + """Set a calendar as the user's default, clearing all others.""" + async with get_async_db_context(db) as db: + # Clear all defaults for this user + await db.execute( + update(Calendar) + .where(Calendar.user_id == user_id, Calendar.is_default == True) + .values(is_default=False) + ) + # Set the new default + result = await db.execute( + select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id) + ) + cal = result.scalars().first() + if not cal: + return None + cal.is_default = True + cal.updated_at = int(time.time_ns()) + await db.commit() + return await self._to_calendar_model(cal, db=db) + async def delete_calendar_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: - """Delete a non-system calendar. Cascades to events, attendees, and grants.""" + """Delete a non-default calendar. Cascades to events, attendees, and grants.""" try: async with get_async_db_context(db) as db: result = await db.execute(select(Calendar).filter(Calendar.id == id)) cal = result.scalars().first() - if not cal or cal.is_system: + if not cal or cal.is_default: return False # Delete attendees for all events in this calendar diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 4b052bd754..220edf853c 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -310,10 +310,16 @@ async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verifi if cal.user_id != user.id and user.role != 'admin': raise HTTPException(status_code=403, detail='Only owner can delete calendar') - if cal.is_system: - raise HTTPException(status_code=400, detail='Cannot delete system calendar') result = await Calendars.delete_calendar_by_id(calendar_id) if not result: raise HTTPException(status_code=500, detail='Failed to delete') return {'status': True} + + +@router.post('/{calendar_id}/default') +async def set_default_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): + cal = await Calendars.set_default_calendar(user.id, calendar_id) + if not cal: + raise HTTPException(status_code=404, detail='Calendar not found') + return cal diff --git a/src/lib/apis/calendar/index.ts b/src/lib/apis/calendar/index.ts index 39540bb890..fa75f9a6a7 100644 --- a/src/lib/apis/calendar/index.ts +++ b/src/lib/apis/calendar/index.ts @@ -5,7 +5,7 @@ export type CalendarModel = { user_id: string; name: string; color: string | null; - is_system: boolean; + is_default: boolean; data: Record | null; meta: Record | null; access_grants: any[]; @@ -188,6 +188,37 @@ export const deleteCalendar = async (token: string, calendarId: string): Promise return res?.status ?? false; }; +export const setDefaultCalendar = async ( + token: string, + calendarId: string +): Promise => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/calendars/${calendarId}/default`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + // ── Events ───────────────────────────────── export const getCalendarEvents = async ( diff --git a/src/routes/(app)/calendar/+page.svelte b/src/routes/(app)/calendar/+page.svelte index 89940ef23b..25671fc791 100644 --- a/src/routes/(app)/calendar/+page.svelte +++ b/src/routes/(app)/calendar/+page.svelte @@ -128,7 +128,7 @@ showEventModal = true; } - $: defaultCalendarId = calendars.find((c) => !c.is_system || c.name === 'Personal')?.id || calendars[0]?.id || ''; + $: defaultCalendarId = calendars.find((c) => c.is_default)?.id || calendars[0]?.id || ''; onMount(async () => { await loadCalendars(); From f0ec5ee08ff6978131b3d657c8a8bc98c8533d05 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 21:49:48 +0900 Subject: [PATCH 058/119] refac --- src/lib/components/calendar/CalendarSidebar.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/components/calendar/CalendarSidebar.svelte b/src/lib/components/calendar/CalendarSidebar.svelte index 09e604a281..8ed0df4727 100644 --- a/src/lib/components/calendar/CalendarSidebar.svelte +++ b/src/lib/components/calendar/CalendarSidebar.svelte @@ -72,7 +72,7 @@
-
{miniMonthNames[miniMonth]} {miniYear}
+
{miniMonthNames[miniMonth]} {miniYear}
-
+
{#each ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as d}
{d}
{/each}
-
+
{#each miniDays as day}
+ + diff --git a/src/lib/components/workspace/Models/BuiltinTools.svelte b/src/lib/components/workspace/Models/BuiltinTools.svelte index b900d004b3..fe99005688 100644 --- a/src/lib/components/workspace/Models/BuiltinTools.svelte +++ b/src/lib/components/workspace/Models/BuiltinTools.svelte @@ -50,6 +50,10 @@ automations: { label: $i18n.t('Automations'), description: $i18n.t('Create and manage scheduled automations') + }, + calendar: { + label: $i18n.t('Calendar'), + description: $i18n.t('List calendars, search, create, update, and delete calendar events') } }; From f45d0f130ef9c3d3958f54320b987f2e2eacc421 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 22:22:15 +0900 Subject: [PATCH 060/119] refac --- backend/open_webui/config.py | 6 +++ backend/open_webui/main.py | 3 ++ backend/open_webui/routers/auths.py | 4 ++ backend/open_webui/routers/calendar.py | 49 ++++++++++++++----- backend/open_webui/utils/tools.py | 5 +- .../components/admin/Settings/General.svelte | 8 +++ .../components/layout/Sidebar/UserMenu.svelte | 2 +- 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index a68720a7c0..1ee107a6ac 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1624,6 +1624,12 @@ ENABLE_CHANNELS = PersistentConfig( os.environ.get('ENABLE_CHANNELS', 'False').lower() == 'true', ) +ENABLE_CALENDAR = PersistentConfig( + 'ENABLE_CALENDAR', + 'calendar.enable', + os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true', +) + AUTOMATION_MAX_COUNT = PersistentConfig( 'AUTOMATION_MAX_COUNT', 'automations.max_count', diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index c13250c587..23379a7750 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -396,6 +396,7 @@ from open_webui.config import ( AUTOMATION_MAX_COUNT, AUTOMATION_MIN_INTERVAL, ENABLE_CHANNELS, + ENABLE_CALENDAR, ENABLE_NOTES, ENABLE_USER_STATUS, ENABLE_COMMUNITY_SHARING, @@ -902,6 +903,7 @@ app.state.config.FOLDER_MAX_FILE_COUNT = FOLDER_MAX_FILE_COUNT app.state.config.AUTOMATION_MAX_COUNT = AUTOMATION_MAX_COUNT app.state.config.AUTOMATION_MIN_INTERVAL = AUTOMATION_MIN_INTERVAL app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS +app.state.config.ENABLE_CALENDAR = ENABLE_CALENDAR app.state.config.ENABLE_NOTES = ENABLE_NOTES app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING app.state.config.ENABLE_MESSAGE_RATING = ENABLE_MESSAGE_RATING @@ -2218,6 +2220,7 @@ async def get_app_config(request: Request): 'enable_folders': app.state.config.ENABLE_FOLDERS, 'folder_max_file_count': app.state.config.FOLDER_MAX_FILE_COUNT, 'enable_channels': app.state.config.ENABLE_CHANNELS, + 'enable_calendar': app.state.config.ENABLE_CALENDAR, 'enable_notes': app.state.config.ENABLE_NOTES, 'enable_web_search': app.state.config.ENABLE_WEB_SEARCH, 'enable_code_execution': app.state.config.ENABLE_CODE_EXECUTION, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 651e123b64..c8daa4957a 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -972,6 +972,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, + 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, 'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES, 'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS, @@ -1000,6 +1001,7 @@ class AdminConfig(BaseModel): AUTOMATION_MAX_COUNT: Optional[int | str] = None AUTOMATION_MIN_INTERVAL: Optional[int | str] = None ENABLE_CHANNELS: bool + ENABLE_CALENDAR: bool ENABLE_MEMORIES: bool ENABLE_NOTES: bool ENABLE_USER_WEBHOOKS: bool @@ -1031,6 +1033,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep int(form_data.AUTOMATION_MIN_INTERVAL) if form_data.AUTOMATION_MIN_INTERVAL else '' ) request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS + request.app.state.config.ENABLE_CALENDAR = form_data.ENABLE_CALENDAR request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES request.app.state.config.ENABLE_NOTES = form_data.ENABLE_NOTES @@ -1074,6 +1077,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, + 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, 'ENABLE_NOTES': request.app.state.config.ENABLE_NOTES, 'ENABLE_USER_WEBHOOKS': request.app.state.config.ENABLE_USER_WEBHOOKS, diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 220edf853c..f92f5b8943 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -2,7 +2,7 @@ import logging import time from typing import Optional -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel from open_webui.models.calendar import ( @@ -24,12 +24,22 @@ from open_webui.models.groups import Groups from open_webui.models.users import UserModel from open_webui.utils.auth import get_verified_user from open_webui.utils.calendar import expand_recurring_event +from open_webui.constants import ERROR_MESSAGES log = logging.getLogger(__name__) router = APIRouter() +async def check_calendar_enabled(request: Request): + """Dependency to ensure calendar feature is globally enabled.""" + if not request.app.state.config.ENABLE_CALENDAR: + raise HTTPException( + status_code=403, + detail=ERROR_MESSAGES.FEATURE_DISABLED('Calendar'), + ) + + async def _check_calendar_access( calendar_id: str, user: UserModel, permission: str = 'write' ) -> CalendarModel: @@ -58,14 +68,16 @@ async def _check_calendar_access( @router.get('/', response_model=list[CalendarModel]) -async def get_calendars(user: UserModel = Depends(get_verified_user)): +async def get_calendars(request: Request, user: UserModel = Depends(get_verified_user)): """List user's calendars (owned + shared). Auto-creates defaults on first call.""" + await check_calendar_enabled(request) return await Calendars.get_calendars_by_user(user.id) @router.post('/create', response_model=CalendarModel) -async def create_calendar(form_data: CalendarForm, user: UserModel = Depends(get_verified_user)): +async def create_calendar(request: Request, form_data: CalendarForm, user: UserModel = Depends(get_verified_user)): """Create a new user calendar.""" + await check_calendar_enabled(request) return await Calendars.insert_new_calendar(user.id, form_data) @@ -76,6 +88,7 @@ async def create_calendar(form_data: CalendarForm, user: UserModel = Depends(get @router.get('/events') async def get_events( + request: Request, start: str, end: str, calendar_ids: Optional[str] = None, @@ -92,6 +105,7 @@ async def get_events( - Stored events from the database - Virtual events computed from active automation RRULEs (Scheduled Tasks calendar) """ + await check_calendar_enabled(request) from datetime import datetime try: @@ -203,25 +217,29 @@ async def get_events( @router.post('/events/create', response_model=CalendarEventModel) -async def create_event(form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)): +async def create_event(request: Request, form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) await _check_calendar_access(form_data.calendar_id, user, 'write') return await CalendarEvents.insert_new_event(user.id, form_data) @router.get('/events/search', response_model=CalendarEventListResponse) async def search_events( + request: Request, query: Optional[str] = None, skip: int = 0, limit: int = 30, user: UserModel = Depends(get_verified_user), ): + await check_calendar_enabled(request) return await CalendarEvents.search_events( user_id=user.id, query=query, skip=skip, limit=limit ) @router.get('/events/{event_id}', response_model=CalendarEventModel) -async def get_event(event_id: str, user: UserModel = Depends(get_verified_user)): +async def get_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -233,8 +251,9 @@ async def get_event(event_id: str, user: UserModel = Depends(get_verified_user)) @router.post('/events/{event_id}/update', response_model=CalendarEventModel) async def update_event( - event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) + request: Request, event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) ): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -248,7 +267,8 @@ async def update_event( @router.delete('/events/{event_id}/delete') -async def delete_event(event_id: str, user: UserModel = Depends(get_verified_user)): +async def delete_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -263,9 +283,10 @@ async def delete_event(event_id: str, user: UserModel = Depends(get_verified_use @router.post('/events/{event_id}/rsvp', response_model=dict) async def rsvp_event( - event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user) + request: Request, event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user) ): """Update own RSVP status for an event.""" + await check_calendar_enabled(request) if form_data.status not in ('accepted', 'declined', 'tentative', 'pending'): raise HTTPException(status_code=400, detail='Invalid status') @@ -281,15 +302,17 @@ async def rsvp_event( @router.get('/{calendar_id}', response_model=CalendarModel) -async def get_calendar_by_id(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def get_calendar_by_id(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'read') return cal @router.post('/{calendar_id}/update', response_model=CalendarModel) async def update_calendar( - calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user) + request: Request, calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user) ): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can change access grants @@ -303,7 +326,8 @@ async def update_calendar( @router.delete('/{calendar_id}/delete') -async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can delete @@ -318,7 +342,8 @@ async def delete_calendar(calendar_id: str, user: UserModel = Depends(get_verifi @router.post('/{calendar_id}/default') -async def set_default_calendar(calendar_id: str, user: UserModel = Depends(get_verified_user)): +async def set_default_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): + await check_calendar_enabled(request) cal = await Calendars.set_default_calendar(user.id, calendar_id) if not cal: raise HTTPException(status_code=404, detail='Calendar not found') diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 52a8868391..e0791a35ff 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -557,7 +557,10 @@ async def get_builtin_tools( ) # Calendar tools - search/create/update/delete events - if is_builtin_tool_enabled('calendar'): + if ( + is_builtin_tool_enabled('calendar') + and getattr(request.app.state.config, 'ENABLE_CALENDAR', False) + ): builtin_functions.extend( [search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event] ) diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index f2ba4a3ee1..f535bd68ee 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -756,6 +756,14 @@
+
+
+ {$i18n.t('Calendar')} ({$i18n.t('Beta')}) +
+ + +
+
{$i18n.t('Memories')} ({$i18n.t('Beta')}) diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index 30c29962b4..b159a0dd61 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -250,7 +250,7 @@ {/if} - {#if $user?.role === 'admin' || $user?.permissions?.features?.calendar} + {#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)} Date: Sun, 19 Apr 2026 22:33:32 +0900 Subject: [PATCH 061/119] refac --- backend/open_webui/config.py | 6 ++++++ backend/open_webui/main.py | 3 +++ backend/open_webui/routers/auths.py | 4 ++++ backend/open_webui/routers/automations.py | 5 +++++ backend/open_webui/utils/automations.py | 4 ++++ backend/open_webui/utils/tools.py | 6 +++++- src/lib/components/admin/Settings/General.svelte | 14 +++++++++++--- src/lib/components/layout/Sidebar/UserMenu.svelte | 2 +- src/routes/(app)/automations/+page.svelte | 2 +- src/routes/(app)/automations/[id]/+page.svelte | 4 ++-- 10 files changed, 42 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 1ee107a6ac..53de67387f 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1630,6 +1630,12 @@ ENABLE_CALENDAR = PersistentConfig( os.environ.get('ENABLE_CALENDAR', 'True').lower() == 'true', ) +ENABLE_AUTOMATIONS = PersistentConfig( + 'ENABLE_AUTOMATIONS', + 'automations.enable', + os.environ.get('ENABLE_AUTOMATIONS', 'True').lower() == 'true', +) + AUTOMATION_MAX_COUNT = PersistentConfig( 'AUTOMATION_MAX_COUNT', 'automations.max_count', diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 23379a7750..f9b21e6592 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -393,6 +393,7 @@ from open_webui.config import ( API_KEYS_ALLOWED_ENDPOINTS, ENABLE_FOLDERS, FOLDER_MAX_FILE_COUNT, + ENABLE_AUTOMATIONS, AUTOMATION_MAX_COUNT, AUTOMATION_MIN_INTERVAL, ENABLE_CHANNELS, @@ -900,6 +901,7 @@ app.state.config.BANNERS = WEBUI_BANNERS app.state.config.ENABLE_FOLDERS = ENABLE_FOLDERS app.state.config.FOLDER_MAX_FILE_COUNT = FOLDER_MAX_FILE_COUNT +app.state.config.ENABLE_AUTOMATIONS = ENABLE_AUTOMATIONS app.state.config.AUTOMATION_MAX_COUNT = AUTOMATION_MAX_COUNT app.state.config.AUTOMATION_MIN_INTERVAL = AUTOMATION_MIN_INTERVAL app.state.config.ENABLE_CHANNELS = ENABLE_CHANNELS @@ -2221,6 +2223,7 @@ async def get_app_config(request: Request): 'folder_max_file_count': app.state.config.FOLDER_MAX_FILE_COUNT, 'enable_channels': app.state.config.ENABLE_CHANNELS, 'enable_calendar': app.state.config.ENABLE_CALENDAR, + 'enable_automations': app.state.config.ENABLE_AUTOMATIONS, 'enable_notes': app.state.config.ENABLE_NOTES, 'enable_web_search': app.state.config.ENABLE_WEB_SEARCH, 'enable_code_execution': app.state.config.ENABLE_CODE_EXECUTION, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index c8daa4957a..d3337d8109 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -971,6 +971,7 @@ async def get_admin_config(request: Request, user=Depends(get_admin_user)): 'FOLDER_MAX_FILE_COUNT': request.app.state.config.FOLDER_MAX_FILE_COUNT, 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, + 'ENABLE_AUTOMATIONS': request.app.state.config.ENABLE_AUTOMATIONS, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, @@ -1000,6 +1001,7 @@ class AdminConfig(BaseModel): FOLDER_MAX_FILE_COUNT: Optional[int | str] = None AUTOMATION_MAX_COUNT: Optional[int | str] = None AUTOMATION_MIN_INTERVAL: Optional[int | str] = None + ENABLE_AUTOMATIONS: bool ENABLE_CHANNELS: bool ENABLE_CALENDAR: bool ENABLE_MEMORIES: bool @@ -1032,6 +1034,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep request.app.state.config.AUTOMATION_MIN_INTERVAL = ( int(form_data.AUTOMATION_MIN_INTERVAL) if form_data.AUTOMATION_MIN_INTERVAL else '' ) + request.app.state.config.ENABLE_AUTOMATIONS = form_data.ENABLE_AUTOMATIONS request.app.state.config.ENABLE_CHANNELS = form_data.ENABLE_CHANNELS request.app.state.config.ENABLE_CALENDAR = form_data.ENABLE_CALENDAR request.app.state.config.ENABLE_MEMORIES = form_data.ENABLE_MEMORIES @@ -1076,6 +1079,7 @@ async def update_admin_config(request: Request, form_data: AdminConfig, user=Dep 'FOLDER_MAX_FILE_COUNT': request.app.state.config.FOLDER_MAX_FILE_COUNT, 'AUTOMATION_MAX_COUNT': request.app.state.config.AUTOMATION_MAX_COUNT, 'AUTOMATION_MIN_INTERVAL': request.app.state.config.AUTOMATION_MIN_INTERVAL, + 'ENABLE_AUTOMATIONS': request.app.state.config.ENABLE_AUTOMATIONS, 'ENABLE_CHANNELS': request.app.state.config.ENABLE_CHANNELS, 'ENABLE_CALENDAR': request.app.state.config.ENABLE_CALENDAR, 'ENABLE_MEMORIES': request.app.state.config.ENABLE_MEMORIES, diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index d68bd8e2c6..ed33c4e8cb 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -39,6 +39,11 @@ PAGE_ITEM_COUNT = 30 async def check_automations_permission(request, user): + if not request.app.state.config.ENABLE_AUTOMATIONS: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.UNAUTHORIZED, + ) if user.role != 'admin' and not await has_permission( user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS ): diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 3866eb865a..ac1f4df699 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -126,6 +126,10 @@ async def automation_worker_loop(app) -> None: log.info(f'Automation worker started (poll interval: {AUTOMATION_POLL_INTERVAL}s)') while True: try: + if not getattr(app.state.config, 'ENABLE_AUTOMATIONS', False): + await asyncio.sleep(AUTOMATION_POLL_INTERVAL) + continue + async with get_async_db() as db: batch = await Automations.claim_due(int(time.time_ns()), limit=10, db=db) if batch: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index e0791a35ff..1c47fc75a6 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -551,7 +551,11 @@ async def get_builtin_tools( builtin_functions.extend([create_tasks, update_task]) # Automation tools - create and manage scheduled automations from chat - if is_builtin_tool_enabled('automations') and await has_user_permission('automations'): + if ( + is_builtin_tool_enabled('automations') + and getattr(request.app.state.config, 'ENABLE_AUTOMATIONS', False) + and await has_user_permission('automations') + ): builtin_functions.extend( [create_automation, update_automation, list_automations, toggle_automation, delete_automation] ) diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte index f535bd68ee..ddb81e844b 100644 --- a/src/lib/components/admin/Settings/General.svelte +++ b/src/lib/components/admin/Settings/General.svelte @@ -740,6 +740,14 @@
{/if} +
+
+ {$i18n.t('Memories')} ({$i18n.t('Beta')}) +
+ + +
+
{$i18n.t('Notes')} ({$i18n.t('Beta')}) @@ -758,7 +766,7 @@
- {$i18n.t('Calendar')} ({$i18n.t('Beta')}) + {$i18n.t('Calendar')}
@@ -766,10 +774,10 @@
- {$i18n.t('Memories')} ({$i18n.t('Beta')}) + {$i18n.t('Automations')}
- +
diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index b159a0dd61..dddcf42550 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -214,7 +214,7 @@
{$i18n.t('Settings')}
- {#if $user?.role === 'admin' || $user?.permissions?.features?.automations} + {#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)}
{ - if ($user?.role !== 'admin' && !($user?.permissions?.features?.automations ?? false)) { + if (!$config?.features?.enable_automations || ($user?.role !== 'admin' && !($user?.permissions?.features?.automations ?? false))) { goto('/'); return; } diff --git a/src/routes/(app)/automations/[id]/+page.svelte b/src/routes/(app)/automations/[id]/+page.svelte index 51745fa01e..9d7cf6bbed 100644 --- a/src/routes/(app)/automations/[id]/+page.svelte +++ b/src/routes/(app)/automations/[id]/+page.svelte @@ -4,7 +4,7 @@ import { onMount, getContext } from 'svelte'; import { page } from '$app/stores'; - import { user, showSidebar } from '$lib/stores'; + import { user, showSidebar, config } from '$lib/stores'; import { getAutomationById } from '$lib/apis/automations'; import AutomationEditor from '$lib/components/automations/AutomationEditor.svelte'; @@ -18,7 +18,7 @@ $: automationId = $page.params.id; onMount(async () => { - if ($user?.role !== 'admin' && !($user?.permissions?.features?.automations ?? false)) { + if (!$config?.features?.enable_automations || ($user?.role !== 'admin' && !($user?.permissions?.features?.automations ?? false))) { goto('/'); return; } From 5afc258c5b13f456be528420513ade546c5e86f9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 22:37:10 +0900 Subject: [PATCH 062/119] refac --- backend/open_webui/config.py | 5 ++ backend/open_webui/routers/calendar.py | 45 ++++++++++-------- backend/open_webui/utils/tools.py | 1 + .../admin/Users/Groups/Permissions.svelte | 16 +++++++ static/favicon.png | Bin 10655 -> 21666 bytes static/static/favicon.png | Bin 10655 -> 21666 bytes 6 files changed, 48 insertions(+), 19 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 53de67387f..c43ead1d79 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1524,6 +1524,10 @@ USER_PERMISSIONS_FEATURES_AUTOMATIONS = ( os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true' ) +USER_PERMISSIONS_FEATURES_CALENDAR = ( + os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true' +) + USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true' @@ -1594,6 +1598,7 @@ DEFAULT_USER_PERMISSIONS = { 'code_interpreter': USER_PERMISSIONS_FEATURES_CODE_INTERPRETER, 'memories': USER_PERMISSIONS_FEATURES_MEMORIES, 'automations': USER_PERMISSIONS_FEATURES_AUTOMATIONS, + 'calendar': USER_PERMISSIONS_FEATURES_CALENDAR, }, 'settings': { 'interface': USER_PERMISSIONS_SETTINGS_INTERFACE, diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index f92f5b8943..5fb7cb6f9d 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -2,8 +2,7 @@ import logging import time from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Request -from pydantic import BaseModel +from fastapi import APIRouter, Depends, HTTPException, Request, status from open_webui.models.calendar import ( Calendars, @@ -23,6 +22,7 @@ from open_webui.models.access_grants import AccessGrants from open_webui.models.groups import Groups from open_webui.models.users import UserModel from open_webui.utils.auth import get_verified_user +from open_webui.utils.access_control import has_permission from open_webui.utils.calendar import expand_recurring_event from open_webui.constants import ERROR_MESSAGES @@ -31,12 +31,19 @@ log = logging.getLogger(__name__) router = APIRouter() -async def check_calendar_enabled(request: Request): - """Dependency to ensure calendar feature is globally enabled.""" +async def check_calendar_permission(request: Request, user): + """Check global feature flag AND per-user permission for calendar access.""" if not request.app.state.config.ENABLE_CALENDAR: raise HTTPException( - status_code=403, - detail=ERROR_MESSAGES.FEATURE_DISABLED('Calendar'), + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.UNAUTHORIZED, + ) + if user.role != 'admin' and not await has_permission( + user.id, 'features.calendar', request.app.state.config.USER_PERMISSIONS + ): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.UNAUTHORIZED, ) @@ -70,14 +77,14 @@ async def _check_calendar_access( @router.get('/', response_model=list[CalendarModel]) async def get_calendars(request: Request, user: UserModel = Depends(get_verified_user)): """List user's calendars (owned + shared). Auto-creates defaults on first call.""" - await check_calendar_enabled(request) + await check_calendar_permission(request, user) return await Calendars.get_calendars_by_user(user.id) @router.post('/create', response_model=CalendarModel) async def create_calendar(request: Request, form_data: CalendarForm, user: UserModel = Depends(get_verified_user)): """Create a new user calendar.""" - await check_calendar_enabled(request) + await check_calendar_permission(request, user) return await Calendars.insert_new_calendar(user.id, form_data) @@ -105,7 +112,7 @@ async def get_events( - Stored events from the database - Virtual events computed from active automation RRULEs (Scheduled Tasks calendar) """ - await check_calendar_enabled(request) + await check_calendar_permission(request, user) from datetime import datetime try: @@ -218,7 +225,7 @@ async def get_events( @router.post('/events/create', response_model=CalendarEventModel) async def create_event(request: Request, form_data: CalendarEventForm, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) await _check_calendar_access(form_data.calendar_id, user, 'write') return await CalendarEvents.insert_new_event(user.id, form_data) @@ -231,7 +238,7 @@ async def search_events( limit: int = 30, user: UserModel = Depends(get_verified_user), ): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) return await CalendarEvents.search_events( user_id=user.id, query=query, skip=skip, limit=limit ) @@ -239,7 +246,7 @@ async def search_events( @router.get('/events/{event_id}', response_model=CalendarEventModel) async def get_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -253,7 +260,7 @@ async def get_event(request: Request, event_id: str, user: UserModel = Depends(g async def update_event( request: Request, event_id: str, form_data: CalendarEventUpdateForm, user: UserModel = Depends(get_verified_user) ): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -268,7 +275,7 @@ async def update_event( @router.delete('/events/{event_id}/delete') async def delete_event(request: Request, event_id: str, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) event = await CalendarEvents.get_event_by_id(event_id) if not event: raise HTTPException(status_code=404, detail='Event not found') @@ -286,7 +293,7 @@ async def rsvp_event( request: Request, event_id: str, form_data: RSVPForm, user: UserModel = Depends(get_verified_user) ): """Update own RSVP status for an event.""" - await check_calendar_enabled(request) + await check_calendar_permission(request, user) if form_data.status not in ('accepted', 'declined', 'tentative', 'pending'): raise HTTPException(status_code=400, detail='Invalid status') @@ -303,7 +310,7 @@ async def rsvp_event( @router.get('/{calendar_id}', response_model=CalendarModel) async def get_calendar_by_id(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) cal = await _check_calendar_access(calendar_id, user, 'read') return cal @@ -312,7 +319,7 @@ async def get_calendar_by_id(request: Request, calendar_id: str, user: UserModel async def update_calendar( request: Request, calendar_id: str, form_data: CalendarUpdateForm, user: UserModel = Depends(get_verified_user) ): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can change access grants @@ -327,7 +334,7 @@ async def update_calendar( @router.delete('/{calendar_id}/delete') async def delete_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) cal = await _check_calendar_access(calendar_id, user, 'write') # Only owner/admin can delete @@ -343,7 +350,7 @@ async def delete_calendar(request: Request, calendar_id: str, user: UserModel = @router.post('/{calendar_id}/default') async def set_default_calendar(request: Request, calendar_id: str, user: UserModel = Depends(get_verified_user)): - await check_calendar_enabled(request) + await check_calendar_permission(request, user) cal = await Calendars.set_default_calendar(user.id, calendar_id) if not cal: raise HTTPException(status_code=404, detail='Calendar not found') diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 1c47fc75a6..471ec8540d 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -564,6 +564,7 @@ async def get_builtin_tools( if ( is_builtin_tool_enabled('calendar') and getattr(request.app.state.config, 'ENABLE_CALENDAR', False) + and await has_user_permission('calendar') ): builtin_functions.extend( [search_calendar_events, create_calendar_event, update_calendar_event, delete_calendar_event] diff --git a/src/lib/components/admin/Users/Groups/Permissions.svelte b/src/lib/components/admin/Users/Groups/Permissions.svelte index 7bd8fd00e0..cbfcb67b0a 100644 --- a/src/lib/components/admin/Users/Groups/Permissions.svelte +++ b/src/lib/components/admin/Users/Groups/Permissions.svelte @@ -916,6 +916,22 @@
{/if}
+ +
+
+
+ {$i18n.t('Calendar')} +
+ +
+ {#if defaultPermissions?.features?.calendar && !permissions.features.calendar} +
+
+ {$i18n.t('This is a default user permission and will remain enabled.')} +
+
+ {/if} +

diff --git a/static/favicon.png b/static/favicon.png index 63735ad4616fa452325af0fe351139dca01ca0ab..10c84f440ced21353ee824440758cbd080c7bf55 100644 GIT binary patch literal 21666 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 literal 10655 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 literal 10655 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 Date: Sun, 19 Apr 2026 22:40:59 +0900 Subject: [PATCH 063/119] refac --- static/static/favicon.ico | Bin 15086 -> 4286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/static/static/favicon.ico b/static/static/favicon.ico index 14c5f9c6d437ed109a8579031cf181ee52bdf30e..b819d42f96d1b745d1d815521c0997d588f451ef 100644 GIT binary patch literal 4286 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 bfagL*MEiZNd%>mb_i9fBsuCLy9d!Q>dBe1} From 37eba1c5a66b3145c122a6b40e5c29707526d121 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 22:45:54 +0900 Subject: [PATCH 064/119] chore: format --- backend/open_webui/config.py | 4 +- .../56359461a091_add_calendar_tables.py | 82 ++++----- backend/open_webui/models/automations.py | 13 +- backend/open_webui/models/calendar.py | 98 ++++------- backend/open_webui/routers/calendar.py | 67 ++++--- backend/open_webui/routers/knowledge.py | 4 +- backend/open_webui/static/favicon.ico | Bin 15086 -> 4286 bytes backend/open_webui/static/favicon.png | Bin 10655 -> 21666 bytes backend/open_webui/tools/builtin.py | 13 +- backend/open_webui/utils/middleware.py | 30 +++- src/lib/apis/calendar/index.ts | 1 - .../calendar/CalendarEventChip.svelte | 6 +- .../calendar/CalendarEventModal.svelte | 6 +- .../components/calendar/CalendarView.svelte | 165 +++++++++++++++--- src/lib/components/chat/Chat.svelte | 2 +- .../chat/Messages/ResponseMessage.svelte | 2 +- src/lib/i18n/locales/ar-BH/translation.json | 16 ++ src/lib/i18n/locales/ar/translation.json | 16 ++ src/lib/i18n/locales/az-AZ/translation.json | 16 ++ src/lib/i18n/locales/bg-BG/translation.json | 16 ++ src/lib/i18n/locales/bn-BD/translation.json | 16 ++ src/lib/i18n/locales/bo-TB/translation.json | 16 ++ src/lib/i18n/locales/bs-BA/translation.json | 16 ++ src/lib/i18n/locales/ca-ES/translation.json | 16 ++ src/lib/i18n/locales/ceb-PH/translation.json | 16 ++ src/lib/i18n/locales/cs-CZ/translation.json | 16 ++ src/lib/i18n/locales/da-DK/translation.json | 16 ++ src/lib/i18n/locales/de-DE/translation.json | 16 ++ src/lib/i18n/locales/dg-DG/translation.json | 16 ++ src/lib/i18n/locales/el-GR/translation.json | 16 ++ src/lib/i18n/locales/en-GB/translation.json | 16 ++ src/lib/i18n/locales/en-US/translation.json | 16 ++ src/lib/i18n/locales/es-ES/translation.json | 16 ++ src/lib/i18n/locales/et-EE/translation.json | 16 ++ src/lib/i18n/locales/eu-ES/translation.json | 16 ++ src/lib/i18n/locales/fa-IR/translation.json | 16 ++ src/lib/i18n/locales/fi-FI/translation.json | 16 ++ src/lib/i18n/locales/fr-CA/translation.json | 16 ++ src/lib/i18n/locales/fr-FR/translation.json | 16 ++ src/lib/i18n/locales/gl-ES/translation.json | 16 ++ src/lib/i18n/locales/he-IL/translation.json | 16 ++ src/lib/i18n/locales/hi-IN/translation.json | 16 ++ src/lib/i18n/locales/hr-HR/translation.json | 16 ++ src/lib/i18n/locales/hu-HU/translation.json | 16 ++ src/lib/i18n/locales/id-ID/translation.json | 16 ++ src/lib/i18n/locales/ie-GA/translation.json | 16 ++ src/lib/i18n/locales/it-IT/translation.json | 16 ++ src/lib/i18n/locales/ja-JP/translation.json | 16 ++ src/lib/i18n/locales/ka-GE/translation.json | 16 ++ src/lib/i18n/locales/kab-DZ/translation.json | 16 ++ src/lib/i18n/locales/ko-KR/translation.json | 16 ++ src/lib/i18n/locales/lt-LT/translation.json | 16 ++ src/lib/i18n/locales/lv-LV/translation.json | 16 ++ src/lib/i18n/locales/ms-MY/translation.json | 16 ++ src/lib/i18n/locales/nb-NO/translation.json | 16 ++ src/lib/i18n/locales/nl-NL/translation.json | 16 ++ src/lib/i18n/locales/pa-IN/translation.json | 16 ++ src/lib/i18n/locales/pl-PL/translation.json | 16 ++ src/lib/i18n/locales/pt-BR/translation.json | 16 ++ src/lib/i18n/locales/pt-PT/translation.json | 16 ++ src/lib/i18n/locales/ro-RO/translation.json | 16 ++ src/lib/i18n/locales/ru-RU/translation.json | 16 ++ src/lib/i18n/locales/sk-SK/translation.json | 16 ++ src/lib/i18n/locales/sr-RS/translation.json | 16 ++ src/lib/i18n/locales/sv-SE/translation.json | 16 ++ src/lib/i18n/locales/ta-IN/translation.json | 16 ++ src/lib/i18n/locales/th-TH/translation.json | 16 ++ src/lib/i18n/locales/tk-TM/translation.json | 16 ++ src/lib/i18n/locales/tr-TR/translation.json | 16 ++ src/lib/i18n/locales/ug-CN/translation.json | 16 ++ src/lib/i18n/locales/uk-UA/translation.json | 16 ++ src/lib/i18n/locales/ur-PK/translation.json | 16 ++ .../i18n/locales/uz-Cyrl-UZ/translation.json | 16 ++ .../i18n/locales/uz-Latn-Uz/translation.json | 16 ++ src/lib/i18n/locales/vi-VN/translation.json | 16 ++ src/lib/i18n/locales/zh-CN/translation.json | 16 ++ src/lib/i18n/locales/zh-TW/translation.json | 16 ++ src/routes/(app)/automations/+page.svelte | 5 +- .../(app)/automations/[id]/+page.svelte | 5 +- 79 files changed, 1272 insertions(+), 207 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index c43ead1d79..d2c88cb2fb 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1524,9 +1524,7 @@ USER_PERMISSIONS_FEATURES_AUTOMATIONS = ( os.environ.get('USER_PERMISSIONS_FEATURES_AUTOMATIONS', 'False').lower() == 'true' ) -USER_PERMISSIONS_FEATURES_CALENDAR = ( - os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true' -) +USER_PERMISSIONS_FEATURES_CALENDAR = os.environ.get('USER_PERMISSIONS_FEATURES_CALENDAR', 'True').lower() == 'true' USER_PERMISSIONS_SETTINGS_INTERFACE = os.environ.get('USER_PERMISSIONS_SETTINGS_INTERFACE', 'True').lower() == 'true' diff --git a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py index a0812578c8..e556440f56 100644 --- a/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py +++ b/backend/open_webui/migrations/versions/56359461a091_add_calendar_tables.py @@ -5,6 +5,7 @@ Revises: c1d2e3f4a5b6 Create Date: 2026-04-19 16:20:58.162045 """ + from typing import Sequence, Union from alembic import op @@ -19,52 +20,55 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.create_table('calendar', - sa.Column('id', sa.Text(), nullable=False), - sa.Column('user_id', sa.Text(), nullable=False), - sa.Column('name', sa.Text(), nullable=False), - sa.Column('color', sa.Text(), nullable=True), - sa.Column('is_default', sa.Boolean(), nullable=False), - sa.Column('data', sa.JSON(), nullable=True), - sa.Column('meta', sa.JSON(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=False), - sa.Column('updated_at', sa.BigInteger(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'calendar', + sa.Column('id', sa.Text(), nullable=False), + sa.Column('user_id', sa.Text(), nullable=False), + sa.Column('name', sa.Text(), nullable=False), + sa.Column('color', sa.Text(), nullable=True), + sa.Column('is_default', sa.Boolean(), nullable=False), + sa.Column('data', sa.JSON(), nullable=True), + sa.Column('meta', sa.JSON(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=False), + sa.Column('updated_at', sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint('id'), ) op.create_index('ix_calendar_user', 'calendar', ['user_id'], unique=False) - op.create_table('calendar_event', - sa.Column('id', sa.Text(), nullable=False), - sa.Column('calendar_id', sa.Text(), nullable=False), - sa.Column('user_id', sa.Text(), nullable=False), - sa.Column('title', sa.Text(), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('start_at', sa.BigInteger(), nullable=False), - sa.Column('end_at', sa.BigInteger(), nullable=True), - sa.Column('all_day', sa.Boolean(), nullable=False), - sa.Column('rrule', sa.Text(), nullable=True), - sa.Column('color', sa.Text(), nullable=True), - sa.Column('location', sa.Text(), nullable=True), - sa.Column('data', sa.JSON(), nullable=True), - sa.Column('meta', sa.JSON(), nullable=True), - sa.Column('is_cancelled', sa.Boolean(), nullable=False), - sa.Column('created_at', sa.BigInteger(), nullable=False), - sa.Column('updated_at', sa.BigInteger(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'calendar_event', + sa.Column('id', sa.Text(), nullable=False), + sa.Column('calendar_id', sa.Text(), nullable=False), + sa.Column('user_id', sa.Text(), nullable=False), + sa.Column('title', sa.Text(), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('start_at', sa.BigInteger(), nullable=False), + sa.Column('end_at', sa.BigInteger(), nullable=True), + sa.Column('all_day', sa.Boolean(), nullable=False), + sa.Column('rrule', sa.Text(), nullable=True), + sa.Column('color', sa.Text(), nullable=True), + sa.Column('location', sa.Text(), nullable=True), + sa.Column('data', sa.JSON(), nullable=True), + sa.Column('meta', sa.JSON(), nullable=True), + sa.Column('is_cancelled', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.BigInteger(), nullable=False), + sa.Column('updated_at', sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint('id'), ) op.create_index('ix_calendar_event_calendar', 'calendar_event', ['calendar_id', 'start_at'], unique=False) op.create_index('ix_calendar_event_user_date', 'calendar_event', ['user_id', 'start_at'], unique=False) - op.create_table('calendar_event_attendee', - sa.Column('id', sa.Text(), nullable=False), - sa.Column('event_id', sa.Text(), nullable=False), - sa.Column('user_id', sa.Text(), nullable=False), - sa.Column('status', sa.Text(), nullable=False), - sa.Column('meta', sa.JSON(), nullable=True), - sa.Column('created_at', sa.BigInteger(), nullable=False), - sa.Column('updated_at', sa.BigInteger(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee') + op.create_table( + 'calendar_event_attendee', + sa.Column('id', sa.Text(), nullable=False), + sa.Column('event_id', sa.Text(), nullable=False), + sa.Column('user_id', sa.Text(), nullable=False), + sa.Column('status', sa.Text(), nullable=False), + sa.Column('meta', sa.JSON(), nullable=True), + sa.Column('created_at', sa.BigInteger(), nullable=False), + sa.Column('updated_at', sa.BigInteger(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('event_id', 'user_id', name='uq_event_attendee'), ) op.create_index('ix_calendar_event_attendee_user', 'calendar_event_attendee', ['user_id', 'status'], unique=False) diff --git a/backend/open_webui/models/automations.py b/backend/open_webui/models/automations.py index c7c78a7c8e..05f449ad13 100644 --- a/backend/open_webui/models/automations.py +++ b/backend/open_webui/models/automations.py @@ -153,15 +153,11 @@ class AutomationTable: row = await db.get(Automation, id) return AutomationModel.model_validate(row) if row else None - async def get_active_by_user( - self, user_id: str, db: Optional[AsyncSession] = None - ) -> list[AutomationModel]: + async def get_active_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> list[AutomationModel]: """Get active automations for a user (for calendar RRULE expansion).""" async with get_async_db_context(db) as db: result = await db.execute( - select(Automation) - .filter_by(user_id=user_id, is_active=True) - .order_by(Automation.created_at.desc()) + select(Automation).filter_by(user_id=user_id, is_active=True).order_by(Automation.created_at.desc()) ) return [AutomationModel.model_validate(r) for r in result.scalars().all()] @@ -291,9 +287,8 @@ class AutomationTable: timezone_by_user_id: dict[str, Optional[str]] = {} if user_ids: from open_webui.models.users import User - tz_result = await db.execute( - select(User.id, User.timezone).where(User.id.in_(user_ids)) - ) + + tz_result = await db.execute(select(User.id, User.timezone).where(User.id.in_(user_ids))) timezone_by_user_id = {uid: tz for uid, tz in tz_result.all()} for row in rows: diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index 859632c494..9d2d71a45b 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -232,9 +232,7 @@ class CalendarEventListResponse(BaseModel): class CalendarTable: - async def _get_access_grants( - self, calendar_id: str, db: Optional[AsyncSession] = None - ) -> list[AccessGrantModel]: + async def _get_access_grants(self, calendar_id: str, db: Optional[AsyncSession] = None) -> list[AccessGrantModel]: return await AccessGrants.get_grants_by_resource('calendar', calendar_id, db=db) async def _to_calendar_model( @@ -245,15 +243,11 @@ class CalendarTable: ) -> CalendarModel: cal_data = CalendarModel.model_validate(cal).model_dump(exclude={'access_grants'}) cal_data['access_grants'] = ( - access_grants - if access_grants is not None - else await self._get_access_grants(cal_data['id'], db=db) + access_grants if access_grants is not None else await self._get_access_grants(cal_data['id'], db=db) ) return CalendarModel.model_validate(cal_data) - async def get_or_create_defaults( - self, user_id: str, db: Optional[AsyncSession] = None - ) -> list[CalendarModel]: + async def get_or_create_defaults(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]: """Return user's calendars, creating 'Personal' and 'Scheduled Tasks' if none exist.""" async with get_async_db_context(db) as db: result = await db.execute( @@ -289,9 +283,7 @@ class CalendarTable: await db.commit() return [CalendarModel.model_validate(c) for c in defaults] - async def get_calendars_by_user( - self, user_id: str, db: Optional[AsyncSession] = None - ) -> list[CalendarModel]: + async def get_calendars_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]: """Owned + shared calendars.""" async with get_async_db_context(db) as db: user_groups = await Groups.get_groups_by_member_id(user_id, db=db) @@ -317,14 +309,9 @@ class CalendarTable: cal_ids = [c.id for c in calendars] grants_map = await AccessGrants.get_grants_by_resources('calendar', cal_ids, db=db) - return [ - await self._to_calendar_model(c, access_grants=grants_map.get(c.id, []), db=db) - for c in calendars - ] + return [await self._to_calendar_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in calendars] - async def get_calendar_by_id( - self, id: str, db: Optional[AsyncSession] = None - ) -> Optional[CalendarModel]: + async def get_calendar_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[CalendarModel]: async with get_async_db_context(db) as db: result = await db.execute(select(Calendar).filter(Calendar.id == id)) cal = result.scalars().first() @@ -414,9 +401,7 @@ class CalendarTable: .values(is_default=False) ) # Set the new default - result = await db.execute( - select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id) - ) + result = await db.execute(select(Calendar).filter(Calendar.id == calendar_id, Calendar.user_id == user_id)) cal = result.scalars().first() if not cal: return None @@ -435,15 +420,11 @@ class CalendarTable: return False # Delete attendees for all events in this calendar - event_ids_result = await db.execute( - select(CalendarEvent.id).filter(CalendarEvent.calendar_id == id) - ) + event_ids_result = await db.execute(select(CalendarEvent.id).filter(CalendarEvent.calendar_id == id)) event_ids = [r[0] for r in event_ids_result.all()] if event_ids: await db.execute( - delete(CalendarEventAttendee).filter( - CalendarEventAttendee.event_id.in_(event_ids) - ) + delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids)) ) # Delete events @@ -465,9 +446,7 @@ class CalendarEventTable: self, event_id: str, db: Optional[AsyncSession] = None ) -> list[CalendarEventAttendeeModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id) - ) + result = await db.execute(select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)) rows = result.scalars().all() return [CalendarEventAttendeeModel.model_validate(r) for r in rows] @@ -515,9 +494,7 @@ class CalendarEventTable: return await self._to_event_model(event, db=db) - async def get_event_by_id( - self, id: str, db: Optional[AsyncSession] = None - ) -> Optional[CalendarEventModel]: + async def get_event_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[CalendarEventModel]: async with get_async_db_context(db) as db: result = await db.execute(select(CalendarEvent).filter(CalendarEvent.id == id)) event = result.scalars().first() @@ -559,9 +536,7 @@ class CalendarEventTable: # Also get event IDs where user is an attendee attendee_event_ids_result = await db.execute( - select(CalendarEventAttendee.event_id).filter( - CalendarEventAttendee.user_id == user_id - ) + select(CalendarEventAttendee.event_id).filter(CalendarEventAttendee.user_id == user_id) ) attendee_event_ids = [r[0] for r in attendee_event_ids_result.all()] @@ -608,16 +583,12 @@ class CalendarEventTable: # Batch-load attendees for all events in one query (avoid N+1) event_ids = [event.id for event, _user in items] att_result = await db.execute( - select(CalendarEventAttendee).filter( - CalendarEventAttendee.event_id.in_(event_ids) - ) + select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids)) ) att_rows = att_result.scalars().all() att_map: dict[str, list[CalendarEventAttendeeModel]] = {} for a in att_rows: - att_map.setdefault(a.event_id, []).append( - CalendarEventAttendeeModel.model_validate(a) - ) + att_map.setdefault(a.event_id, []).append(CalendarEventAttendeeModel.model_validate(a)) events = [] for event, user in items: @@ -697,16 +668,12 @@ class CalendarEventTable: # Batch-load attendees event_ids = [event.id for event, _user in items] att_result = await db.execute( - select(CalendarEventAttendee).filter( - CalendarEventAttendee.event_id.in_(event_ids) - ) + select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id.in_(event_ids)) ) att_rows = att_result.scalars().all() att_map: dict[str, list[CalendarEventAttendeeModel]] = {} for a in att_rows: - att_map.setdefault(a.event_id, []).append( - CalendarEventAttendeeModel.model_validate(a) - ) + att_map.setdefault(a.event_id, []).append(CalendarEventAttendeeModel.model_validate(a)) events = [] for event, user in items: @@ -732,8 +699,16 @@ class CalendarEventTable: update_data = form_data.model_dump(exclude_unset=True) for field in [ - 'calendar_id', 'title', 'description', 'start_at', 'end_at', - 'all_day', 'rrule', 'color', 'location', 'is_cancelled', + 'calendar_id', + 'title', + 'description', + 'start_at', + 'end_at', + 'all_day', + 'rrule', + 'color', + 'location', + 'is_cancelled', ]: if field in update_data: setattr(event, field, update_data[field]) @@ -750,13 +725,10 @@ class CalendarEventTable: await db.commit() return await self._to_event_model(event, db=db) - async def delete_event_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: try: async with get_async_db_context(db) as db: - await db.execute( - delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == id) - ) + await db.execute(delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == id)) await db.execute(delete(CalendarEvent).filter(CalendarEvent.id == id)) await db.commit() return True @@ -774,9 +746,7 @@ class CalendarEventAttendeeTable: """ async with get_async_db_context(db) as db: # Remove existing - await db.execute( - delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id) - ) + await db.execute(delete(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)) now = int(time.time_ns()) models = [] @@ -819,20 +789,14 @@ class CalendarEventAttendeeTable: self, event_id: str, db: Optional[AsyncSession] = None ) -> list[CalendarEventAttendeeModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id) - ) + result = await db.execute(select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)) return [CalendarEventAttendeeModel.model_validate(r) for r in result.scalars().all()] - async def get_events_by_attendee( - self, user_id: str, db: Optional[AsyncSession] = None - ) -> list[str]: + async def get_events_by_attendee(self, user_id: str, db: Optional[AsyncSession] = None) -> list[str]: """Return event IDs where user is an attendee.""" async with get_async_db_context(db) as db: result = await db.execute( - select(CalendarEventAttendee.event_id).filter( - CalendarEventAttendee.user_id == user_id - ) + select(CalendarEventAttendee.event_id).filter(CalendarEventAttendee.user_id == user_id) ) return [r[0] for r in result.all()] diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 5fb7cb6f9d..47093ca788 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -47,9 +47,7 @@ async def check_calendar_permission(request: Request, user): ) -async def _check_calendar_access( - calendar_id: str, user: UserModel, permission: str = 'write' -) -> CalendarModel: +async def _check_calendar_access(calendar_id: str, user: UserModel, permission: str = 'write') -> CalendarModel: """Verify user has access to a calendar. Returns the calendar or raises 403/404.""" cal = await Calendars.get_calendar_by_id(calendar_id) if not cal: @@ -139,9 +137,7 @@ async def get_events( for event in events: event_dict = event.model_dump() if event_dict.get('rrule'): - instances = expand_recurring_event( - event_dict, start_ns, end_ns, tz=user.timezone - ) + instances = expand_recurring_event(event_dict, start_ns, end_ns, tz=user.timezone) for inst in instances: expanded.append(CalendarEventUserResponse(**{**inst, 'user': event.user})) else: @@ -189,34 +185,34 @@ async def get_events( expanded.append(CalendarEventUserResponse(**inst)) # Past runs: single range query joined with automation - runs_with_auto = await AutomationRuns.get_runs_by_user_range( - user.id, start_ns, end_ns - ) + runs_with_auto = await AutomationRuns.get_runs_by_user_range(user.id, start_ns, end_ns) for run, auto in runs_with_auto: - expanded.append(CalendarEventUserResponse( - id=f'run_{run.id}', - calendar_id=scheduled_cal.id, - user_id=user.id, - title=auto.name, - description=run.error if run.status == 'error' else '', - start_at=run.created_at, - end_at=None, - all_day=False, - color=None, - location=None, - data=None, - meta={ - 'automation_id': auto.id, - 'run_id': run.id, - 'chat_id': run.chat_id, - 'status': run.status, - }, - is_cancelled=False, - attendees=[], - created_at=run.created_at, - updated_at=run.created_at, - user=None, - )) + expanded.append( + CalendarEventUserResponse( + id=f'run_{run.id}', + calendar_id=scheduled_cal.id, + user_id=user.id, + title=auto.name, + description=run.error if run.status == 'error' else '', + start_at=run.created_at, + end_at=None, + all_day=False, + color=None, + location=None, + data=None, + meta={ + 'automation_id': auto.id, + 'run_id': run.id, + 'chat_id': run.chat_id, + 'status': run.status, + }, + is_cancelled=False, + attendees=[], + created_at=run.created_at, + updated_at=run.created_at, + user=None, + ) + ) except Exception as e: log.warning(f'Failed to compute automation events: {e}', exc_info=True) @@ -239,9 +235,7 @@ async def search_events( user: UserModel = Depends(get_verified_user), ): await check_calendar_permission(request, user) - return await CalendarEvents.search_events( - user_id=user.id, query=query, skip=skip, limit=limit - ) + return await CalendarEvents.search_events(user_id=user.id, query=query, skip=skip, limit=limit) @router.get('/events/{event_id}', response_model=CalendarEventModel) @@ -341,7 +335,6 @@ async def delete_calendar(request: Request, calendar_id: str, user: UserModel = if cal.user_id != user.id and user.role != 'admin': raise HTTPException(status_code=403, detail='Only owner can delete calendar') - result = await Calendars.delete_calendar_by_id(calendar_id) if not result: raise HTTPException(status_code=500, detail='Failed to delete') diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index d8d92b2428..f503169fc0 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -539,9 +539,7 @@ async def update_knowledge_access_by_id( 'sharing.public_knowledge', ) - knowledge.access_grants = await AccessGrants.set_access_grants( - 'knowledge', id, form_data.access_grants, db=db - ) + knowledge.access_grants = await AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db) return KnowledgeFilesResponse( **knowledge.model_dump(), diff --git a/backend/open_webui/static/favicon.ico b/backend/open_webui/static/favicon.ico index 14c5f9c6d437ed109a8579031cf181ee52bdf30e..b819d42f96d1b745d1d815521c0997d588f451ef 100644 GIT binary patch literal 4286 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 bfagL*MEiZNd%>mb_i9fBsuCLy9d!Q>dBe1} diff --git a/backend/open_webui/static/favicon.png b/backend/open_webui/static/favicon.png index 63735ad4616fa452325af0fe351139dca01ca0ab..10c84f440ced21353ee824440758cbd080c7bf55 100644 GIT binary patch literal 21666 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 literal 10655 zcmX9^cU%+C(>_25B@{)Pbd@4V5v8{PDgp|KG${d81cA^461oU{1r?R1v;+m|9TF*_ zibzcq>0K$I1tfvcdC%Yb$9Ilg_G7OnzUp>;zqrcAho|TK|xn)$5Sne|zqd3o3ORjr**BSj}{woxl zWZfFTK89gk?L{v<8Thbt-}6n2zfYy`Q7vt3sV@d#`qOf9oLTE*Ixd?gB8p2&8u#1_ zsQMj1+xrQ};dOmPbrtX@uFDy^Pd8^xfS66OD7@#h2Db6!9ltY)QRHc|h)JLT*(3=) z(bb!zoEv$%b($rRWF$DAZ2LS&N9T13oz7!{9lrHHGbTPxgqG5{9gk0GOy;t0Jh|MyAHqyF=y67Oef)@yeG{+A66jtx7yc989{PT%xvm{#@{p}+nCrL{0MMKb0m+h%TE$8Q_G(J_GohAIS7tG%8dw3Tg*<(mYuw@P` z9>NlxzI~)8bQvk$q}ZMcv);Tahw_jh78!H@9^AE#He>M4=)|pr^P00noBl}qOpN^R z22V5j(vvrfxu}9O^x3~_#Usuoa&2&UN9*)utQy#c7QpDU&pWXFebRc|um9y9SM-20 ziyzY?-uCQhZW6TT=cD}+uHVqX%e-?D-u>C)r%6zK&Fq}i-6yu(1yF&-rT*=@+nPiE|OROz8bYhLsRoK$?Nu;4tRe%RuwBoyq_r+ACnPJr=cQ7 ztWOt<|CAB$?8Z)?ZO`J*pAy0xLxe9cl~9KU#)uwiZFc| zIklHpAK^27c=fq@%5T?zqvVtI#mJ5}QW4RxuiM)^6*sq2_DPaxSj<9!ZsEkg=Sor( z)l}{1!ykN5j=u=V5;k+$$T; zidm=$b?e)uJyLWpr?g5dZ0=xe3d?$~es^MOwQ|d4cak_D$_|=VoN%`b*{;{tAA9A< zn#L3t5@lUT?4#{X_3*;sRg8WGjC(P}Vbw{zFS`Hs1HbV}1--wPBu3BU3w`+c`>7N6 zIMzHwinl)&5!vc2Q`rJAoXO^8e0O5u?}N$?lep$vkNXVj;6gkoy>XTxEA>jBf42=| z;Q;qq>*@ZC)jE&xT}u+f$ny}glxiD?Q17)hdxd8((L$~DvihxZ_j))P8Z`S}vVS{N zN2*Tht`f|S%g0l8& z1$5?dw5l&Zdedh~r}hE&Dh9^Pc-})X_j=L$+=~_ZiNew&7kTA&2WJaU?Vql@x8~-O zAS`XK6MsY&%lPxtUzZ-z3Gg(!my6cTa++a8ZA0U0C+&4uqBQzD8ur_s)Qg%o!o7Fd z3#Tr6AvD#ZBgEFnf8MuMfP9 zrI}%9h1rW?qQGR=sW3umE-g>{+mNhjRDTm<_}2`hCO1jjyBPB9>y0m606)KKPts81l=k`8MDVg8pO ztjR*P2jR94Ae;(w(tFPU9R^?1nEvRT3y0!U3fGD%GgSfz~Xy{q%N$Tf3E?Ark2h5XI`!n{X(G!LOq&x3h zUUpvB1h%`iS8Dh_{9r^ij%Z1ZBalGhJ@TDM6>N;#&APbeK;M^&kU|@su9GpHrKE4} zGSr>T=+ZtZx>kONuOE+e5(ekLS>`V3bQ%GQydlls-LW;V&J!ofpb898RqDdMLD&_0 zGPZz@sD`lH5dHYT3exZ*2(YVvowK^kcelvHo-8>|XX_tg*qg&;gm<$@9hElYZNihx_S9@A9lJ-+(kA z0|1m#Qv1lDiBG+~Dg^v|Z66s|C6p1@RUyut5njbqinRIL=;lDvgNp!0x3jz;ecYeW z6Ltn5R%G?dR3F$oU~o&dW(>g2L%ZMB*pGH(M9C2vh&qxf--g@89=idp$Bxsw+N>70 zF}SiFP%M|MYhx4@*1Okx9ZQ6xF7yl^+<-b<;%Ttf1iCD~{yLat_#ZC&aa)I!)(D~u zS9XhNxi%!bbpYBn%b6wMg~7Sinb8ArE=0%xw0ePD8U!%O*l(D{J*^4U zV$67^P9Ez~IuPX#^?-SE@u2Qyx4dlkz6Sxy^q}9_C5Y_pbmqm0l0A z{d`fmiPsjnB?EMYqj#}Y!X^4A$^gdHu;?czJXdvS?;3CNI$&NBW3$}FOl`@CPX?k; z{TZ;x(~v-7T4?BhplM=)2C)~y6(MCr;lHe{{|VI^>)OVW|FZ#ZE@G_q+hhWV)PW0u zl`=?pey-;p+AtWodV7p?4v5BiN^4 z0ap=9CH+`Pl}zp)>1x{glO(F!9HynNzPFG3HMh2Q8Qu~hY=A_e&VgsKP{mt&k9@}+ zHa;2RAt-m;RYd_JA0OXe+DxQVvvy`b8M=Ju^2x^h$&O=#GeMT&WO=aQnaDTU_&;%i zr1j5xMy+ojeR=#ea-X`qRmHX;{yj43kk;*apKnvasrm5S7Hr#8>FU8``*3h9SxkUK zo#@+C(jpWD*`PrHWgs8~vmDCmCm3LijIxC)PGI<#Cf22`5(@nV1G?(xCKpUy|wj>XVtksG~$gc+^XuY7au0(IwFu55lmPd5U2;d)KyAZsU@= zX0KQzjqIouvUM@yc+btHIeN|E5#&Nk@ZJMvX=4SGVND216ROHSYJq)8iFytFg!7qs z-H_j0atME1CqJ4io!;SB^+`vDIv6E)8L~Gz^6daCv^KU4F!?M|rVf|`*Mn62wZD&{ z58-Yj2Av!+IEwG2X*Btfn#zx|w@ZOoG2P2$Px+e+5ud#gBcLq~*hL(yt9s5eR0^$O z8{1~r0@Wslqo?I(H3eR4F65z1otAzzh>5<2QlQ%o`&?^^{1rh0uP5^L35vm)($+nj z0}L)oeF$B_a_`1W6)kAHY3!DKbotbPvA}G-X^vFJl`uy^cpXPfB@*k+L)yuy>G8Lc z=kNJs7q2&pGTX=U>|b*>5-}*2Q9yeLGtFJYRw6K-IEs~NrQ3V9C{*AOs@a@@gB(BF z{tcYgRLXvB>XhboH=p~Se!o$GudjXG6t0L^&_+Va1#z!!_tr2}Gx|)33;lZH<(>%} z@fNKY%23)>kso`8E2W3j`Avj>0#T`n3iWIGc7kYV!0L*xq9ATcwG5oZ-E~5FqT;0u zt~@8^P3v^F_f}^nQ$x@Jv#T;WPUdp2-mMP*%3#5|sSyq;mWpQj^wa9mc&S7B)VW-a zP-L~yM0hf5__>$HxW^t`2@{VBmzJYi;a--ve9n;U5rGA_+p~}h%Tel1bZedCVIEw0Io{JPU(P%+#y8_F;hROiRFNbOEjAr09Td@H+`YZg=Nn)pj6 zU_UK4XX%&$|0P(>I~#^`EPHVOhHK*49dWZ1%7fD=KmQrNLZabi!k-`&&4ZL00o~)> zPYXh$Q~1v9?C9xfN5mORDYSE}diLqAr<0#+Ib$8Y`fQneF@vt!!d^q zcYxMVcRu7xzwY(>TX6ypHMAs4HocaQw?~}PO`rze>yI%X`5vdOK^$KxQ z(Ufz3h~xNH2Bq%w*+#>o!`g*9K8y>DkNrFbz3ZPO-DBzMElq80eAwxC3OBdb$6XEE zLpCa_k|zd`EoJ(Ai>Zb9p=rht>n{cs-$v>j>PAG?#?;RpV8py!TyM_sk_ItP1?H7; z%sZ+N1p)@%9fZu8h`A zyF47U68+_0Vad@xH0(jx94Irb|EBeMuF4cUp9l}8cVsWk?--XH(XEsrvVY|%O_TcSa6xp{Ztt^y zPfHp{9EI$H7+e)4`d8etUK+z*o+`1aD4r=E!ew14l=x+}T$v`=NlS$V>pP5jJU!pH z;wKu2 z4T>tt zA;grY7cvizg1%`^uJq&`_=B6gg87T}9eHQ9tT4J%LsFV#N7KB7gj$pl?VCTQWPmL> zb+5DYMv7#|WbT#t^re>cr!UKqfnNqk9yr9pE6)@}1uzfzm3Jjup>kww&UE7CuaTv7vpAMEBN5;#!(G(YF~!_klE#E`U~6+MjgIW;Z5qzhxb#0;#I(5vU0Sa zsaPvHhOZ5KzVmh{qcR$la%SnqEMRR~UqWQFu)r*3gm65%ayh?l8r+GwTG^d&<&I1! z%IlRTu`zFjq9gajaYfgzyu2cs1*iElhFxA#u7-lKk(t;151w_LHaWvK#;G|95AHN} z7^mL|;Ugtu`17@jT+LHd!jx6^uF5R83Jj7ff2RI3C2$Q>mUl{zEtfiMEck9r2wP{& zi_+s2(K_J?XCA@$pUWCv%1#NpV*eY@=1h>MebjY`h@X?qpUScSXt}f?U1U5G6ff9J z(1mvLd`^Mkg5Wt88ZtYdI@oPws78uU^Pksu?;x1hFVq|M?& zSgcUqPBpJNY;JEQ#rLWjx{Vqa5^Izc*1h3jlnzea>nz40er)kSAMIU~r5c$(KQqix zmYpu=^fGnxS)Yc+WJ+|oz5+}qI@NcwWTh!g;t-n64^h2+3MH-+c`Rh$R$?b?{64xe zjspzVEK-OM!uGaY&6~sSrUic&REqKQO;)Drr*M%7XkE?~XJO%lt0DYnT}VT>SgeFs zWnISc&t0X}F7lwnCjFNt`Q&2C6<5 z_;QB+2t2UqIU4i7WN6-pgP8&zjV?th-;>Xia|s)#P;_xRhYydMOeLCbaj$H?3MVo0 zqdb4OC?RF$mWtOooYZYUpE9V>wckgPD_KzE&_bU*ICGedX3^Rb&nfdce-@%zsJ)%ohP2V#H{BbvvFOaUz>D zS{0z3{wF1-lMiW|GmJtOO#3I#^kj3( zc0sLU@) zy^0ER z&|m{U-`E&e8g%Y@3#}{v`zMj2*Vrx`$xeD_rhTedrmh!^u>U zQk?f(&TKLzHRRIZI@9mjgkCJEVb!n zwp}#x@QPZ_8T<8a7bV}4RGxh;Y2)%sX+hs_Vy7Yx)^W3H@TRACElac)*hb#P!RA`6 znHtJGW4jd)R0X<+d3yIo5lE=IFVC?ONlxwl+^{&;;1rEtZ;I2ExYbo=rh z>qTryTei0~30t08^mXn_N{62WHqs;oa?lihpkn-ppMy-z?dLi;d#y z$r}d&etv%SU+;djNKcuKDu1-I4LcD!E5-TY(l&>sfZF{cw(aB8W4tmHLTjj=0(YPn z7A2Jze;wF#&@3B;$;wUCPhe+Q@z%x)EuS0J1ss^oNL`ZSgN8;c88sOn$+ZAhn3UW zvqf<-+OF^2^2s_YtGxyfJ$A>WhSWOyb zdz@+)5jh8VpPeb84;T5@B||1li)*1fS;}rxHVch*iVGPK`6U+9lqSz0)Vj705ha!qrbs#cS3KZ?psb31IZa~vvtmA>@rcRB+UKF@`_qwT|ei0JfIC(|F+bRN{J_1-2Ed+clgBK&sfaO3icosS3QV50?6 ziEXh7-^9XH`E8<0f11pE3(9n`7wxzhHUcN`U8$S)$6_VUt3{>Wz_H>V2rRZ7?|c4| zksX4@KpV%OX~$?aa_t3|l;N8%+plAb+P*z5(UB&~s>ZD>p(@iS69_FPHgDbdKJK_O z#`spIZp%`G6io|l&W}iWBJc{U&$R9TRyeiDduZBV2LoO;m3;3~6n&#B3-+xNl=_Mk znf}Xp->~BA=y-;}nwy(jdfPN2Sn#*l{kW&re`X?2j$Gf5G~8@Xu-5ciA2wc?Q#k>( z`hwQfLLGgSniEMY4ZAvd6_&)4bsxFJ_G(QeP+TX?<4~h4Xmq@5Lg#O!U*moAR4=?+ z>no&a@bMI7C9cmOJ3BvrG3Vc@U4AdfNhyzVa0t53-V;LD>ABj*SgmQR5@BqRXH~c- zX6f_x$?<{Dbdx$u@takWz{-7rYWI+(I;|Tyxj8ch(A}o3^$l=u+42kF5NBvCDbl;FO-GJ}I*tQh%Ld!bi>tSqk{xu-XjY`SwtK38gD%b!*vzW)3VSjH%lYgl-tlTa^hWU@(NN3`B95gdER6bFw zZ#GW8Fj9uru<{maFbcxKVq?Y&r*g0?)S4*UESdE5gfjTpi*7z2Y<^o?{{sU-NCQ^0 zx7rx@*{%Q`vERKZDAe_BeW&(LY)a9#~1$(XzKZq~TyeEds+uLgud-T~m{6cLR z10K;>^0VHiyli6snvf$z!pUpl42vTFQfAgv#FRYG{=!K}>$&Q4ea7bh37Ij41ew&1 zxKswoybETfE~b9HTst)e8(CBiL)wlBcgq|7@AjL;|4$ETAMsY^mj0}<=qsM8-#i#( zZ(5*@u}SmSW~WykFjHxC_A4@7rK5Gf4}R1QpB&Mg^f(sO5{1`=NIQ9h@&8grrIPFf zgj>if2T$RI>g{QYYq%w8*%8K>7QbYfzPb#Dy55lgo_hpo7CCWq85ym8MI@xOK6FSi4PcbaGOZ+oTTB7To2I${z7wQ3Y$!)) zDJ&SUMev3aLt}HsWsYV5pgi>FQebESja9qO3Z-B;MhCkwuycCxi2@JGqnP@Hvgrl@ z;Ms_kiU*54!d+-a;EfJhwr_Bn9={;(1ymAO^^``|m5El%fbWxG+P_AlzFBb~@XdQ8 z=69%Ig8dHY?H7C}uK~_sJN|F_knx4YWEtV8DveuAB6!V)o`;kS42sKA^Nbwf;f;)y3$jBhW~z1)Hz!HHEM%0+N8 zZB>sE9UcREFLirUr-Uz~tLY)+OKq!q^Q!R0+BCQCKF zSG1%(SU?AKseaM>;%dN2H5$$2XjOzhChW@EKpXTozJ#y=hSx~e zp>9N;{WGD1bZTjQccaeJ<3n5nPk)}KQ)_C&jceYhoz%430EEm>m+hEdlA{eI^>0f6 z&L;`(b-@X&RP1{0Hb-kS&!ES~*&+W8HD9JE?F?lXxFs^jn4N0$uYKJA5aU)iQ&6i+ zHCb03w^jl6tsP46_a0#i76o%~w#Wn_Abbz;AMS>u&09H|X=vt<7amj!R@JB6ZGB27 z(J~MtwYm(fNgEvAF9M~glB2yY^8(fiN0AiQP8(A8XA50V5jZ1IsxZ(z z-RM^>Rwq>_>pTA&Tw0XyuX9DNf@h?n4?RHM`TSYY%nDR7jcXju(TbQ(ZJIe$v8tO) zSKknh;*rl#jO67hF4uQ@XV@{o0PrqWePItyEiNP8yO)o?#(BR;@Erp`ZY6HEyrf9qkD*^ zk9z0&zU9?v!ElUJ=r6j2om>{mAisL_w1>X7)Xe%_h_p#vu-U{f_YtDa(;p6ebg)ZX z>IXN#ns0NS_8ocIZLp&Hh+-=wbd^)Sh?rh0#kx>}4*?V{DO`)Hg+v>7v*g}@8L$xh zrqAGg#YLiz_%AvpH<9*3Yx*M&ePL(CMe)9ki^L5Oe(epyaQbj|ec@Sm5FR;C9J%<5 zjst|l5cJ^;AnXjns^^KSAiVPren=nA22SDw!noggU*u|A+y^2cBpaOq9qz@6ySW<} z?E`IDWbhM)(f);3*5enxj6GuwhwLSeOk+0;fXcEa^xjpMX2Iy`n8ozX+K{JwfY%Cc zM?FQDg_?7%<&BR)A%oX%N6O@00Flj;r0qF8L`kQN0JHKwpb>~tZ(=s|xK|W1<7YBg z?*f&VN4g`u+QWFM#+Z#4_!U64uS0}_uZnAyG6@}8qJ!b&5F%@_u$fOge5$y*Y)I?V zg<-f$vm1RlVI04r8^`k0Im`du(Jv9oQB7O~Z2i=sf{QIjp%c?n(`qy$M4ZUkS1F`O zPqDpS-8j=p2eKO83pN6&nVk>tlL3>_EYu6%&3y<_xN?qP7nA&y4}&h+kqw7yXU!h^ zRc9fQ7|Y>a2n@p+1^#uWR7xPYY9}GiPg?eU=*(YFIvwAB&lv*~{V%8mZ zf}IlRxJiue_M0J*+fM+Y;mf=(V32RTmQ32CY}k^y@g*45!JzX6>Uv3d_AR2JN2W!@A7k6q8uE;*bV0#z7WL2)n=) zc~BCTtF63`U7q33$xw|iTdh_+^n1tMinv{Ab1N2}i?xfKaHyc4E!GlRth+k8dVwgr zn4<37t;MUS!&ZEeXoz9i!BsH_IfsFVtgE9lB|WPkqeov*YvAHK z83>Aaq)iZ}-7At3FVWt1H%feni}yedt`eT_?Sj>iQbY$jZW}3`jr2dIF+7s%>sF<< zj1YO50b43j84U=j6K}jB(CcGr^tsmtK{AvPU~rX9Z-6!K_ZM-rw11%_bfE1G zNXohq>acow_8gA}&HuNM(9mVe`BFVhjY=B_L>cVPbV@_eGe%qLcL5z0!TT&rjyVuJ z%t1Ih`}ub+XARf<8Vpl&^ME!*#SCSz>pe90uxA)9KG5X5M^qTlM52j#B5$&<)_bX z^RV9<o~ebxwbuQsSZ{9Gb3 zICOH%GHwTCN`y&%y#!LgWY%lL-~@y10gsx+{j-Sxn$c1pb5Cwavn=GY%%)dC=IsJI z`s^RJcA*55))7w;o083tR_1>cJNDgVTgy$e3EU+!>uL13A>DxcKQ5l!K6Lv@7~8Iq z;zi1rbF+MQq)IX390!62J#vj#9PLvw&-qYfrd@MJC6*(Yi&UALZ`@om+vqVZK%Mx| zb^ePkE-9oxR@Lh!#=tD?S#xMu19fM#BSK_`!+TF?M&H5L!0eQU&@Jv=$4ayDRlkE> z_vNbV6x-)W+OB#z(=J~w`+*eE1vV}dc_z_!qul6VxoiE;qh- dict: } - - - async def search_calendar_events( query: Optional[str] = None, start: Optional[str] = None, @@ -2915,7 +2912,11 @@ async def search_calendar_events( return json.dumps({'error': f'Invalid start datetime: {e}'}) try: - end_ns = _dt_to_ns(end, tz) if end else int(time.time() * 1_000) * 1_000_000 + 365 * 86400 * 1_000_000_000_000 + end_ns = ( + _dt_to_ns(end, tz) + if end + else int(time.time() * 1_000) * 1_000_000 + 365 * 86400 * 1_000_000_000_000 + ) except (ValueError, TypeError) as e: return json.dumps({'error': f'Invalid end datetime: {e}'}) @@ -2929,7 +2930,8 @@ async def search_calendar_events( if query: q = query.lower() items = [ - e for e in items + e + for e in items if q in (e.title or '').lower() or q in (e.description or '').lower() or q in (e.location or '').lower() @@ -3229,4 +3231,3 @@ async def delete_calendar_event( except Exception as e: log.exception(f'delete_calendar_event error: {e}') return json.dumps({'error': str(e)}) - diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index d44112b4ec..036dc9bf39 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -427,7 +427,11 @@ def _render_openai_tool_call_handler(item: dict, done: bool) -> str: if atype == 'search': queries = action.get('queries') or [] query = action.get('query', '') - summary = f'Search: {", ".join(str(q) for q in queries)}' if queries else (f'Search: {query}' if query else '') + summary = ( + f'Search: {", ".join(str(q) for q in queries)}' + if queries + else (f'Search: {query}' if query else '') + ) elif atype == 'open_page': summary = f'Open page: {action.get("url", "")}' if action.get('url') else '' elif atype == 'find_in_page': @@ -490,9 +494,13 @@ def serialize_output(output: list) -> str: files = result_item.get('files') embeds = result_item.get('embeds', '') - parts.append(f'
\nTool Executed\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n
') + parts.append( + f'
\nTool Executed\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n
' + ) else: - parts.append(f'
\nExecuting...\n
') + parts.append( + f'
\nExecuting...\n
' + ) elif item_type == 'function_call_output': # Already handled inline with function_call above @@ -529,9 +537,13 @@ def serialize_output(output: list) -> str: ) if status == 'completed' or duration is not None or not is_last_item: - parts.append(f'
\nThought for {duration or 0} seconds\n{display}\n
') + parts.append( + f'
\nThought for {duration or 0} seconds\n{display}\n
' + ) else: - parts.append(f'
\nThinking…\n{display}\n
') + parts.append( + f'
\nThinking…\n{display}\n
' + ) elif item_type == 'open_webui:code_interpreter': # Code interpreter needs to inspect/mutate prior accumulated content @@ -570,9 +582,13 @@ def serialize_output(output: list) -> str: output_attr = f' output="{html.escape(output_json)}"' if status == 'completed' or duration is not None or not is_last_item: - parts.append(f'
\nAnalyzed\n{display}\n
') + parts.append( + f'
\nAnalyzed\n{display}\n
' + ) else: - parts.append(f'
\nAnalyzing…\n{display}\n
') + parts.append( + f'
\nAnalyzing…\n{display}\n
' + ) return '\n'.join(parts).strip() diff --git a/src/lib/apis/calendar/index.ts b/src/lib/apis/calendar/index.ts index fa75f9a6a7..b496a42bc2 100644 --- a/src/lib/apis/calendar/index.ts +++ b/src/lib/apis/calendar/index.ts @@ -418,7 +418,6 @@ export const rsvpCalendarEvent = async ( return res; }; - export const searchCalendarEvents = async ( token: string, query: string | null, diff --git a/src/lib/components/calendar/CalendarEventChip.svelte b/src/lib/components/calendar/CalendarEventChip.svelte index c63eac1063..b1c3552e6c 100644 --- a/src/lib/components/calendar/CalendarEventChip.svelte +++ b/src/lib/components/calendar/CalendarEventChip.svelte @@ -21,7 +21,11 @@ style="background-color: {event.color || calendarColor || '#3b82f6'};" > - {#if !event.all_day}{new Date(event.start_at / 1_000_000).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }).replace(' ', '')}{/if} + {#if !event.all_day}{new Date(event.start_at / 1_000_000) + .toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }) + .replace(' ', '')}{/if} {event.title} diff --git a/src/lib/components/calendar/CalendarEventModal.svelte b/src/lib/components/calendar/CalendarEventModal.svelte index 5540bda2fe..020a89f923 100644 --- a/src/lib/components/calendar/CalendarEventModal.svelte +++ b/src/lib/components/calendar/CalendarEventModal.svelte @@ -8,7 +8,11 @@ import Spinner from '$lib/components/common/Spinner.svelte'; import type { CalendarModel, CalendarEventModel, CalendarEventForm } from '$lib/apis/calendar'; - import { createCalendarEvent, updateCalendarEvent, deleteCalendarEvent } from '$lib/apis/calendar'; + import { + createCalendarEvent, + updateCalendarEvent, + deleteCalendarEvent + } from '$lib/apis/calendar'; const i18n = getContext('i18n'); const dispatch = createEventDispatcher(); diff --git a/src/lib/components/calendar/CalendarView.svelte b/src/lib/components/calendar/CalendarView.svelte index 0b67d7de98..5100a58b94 100644 --- a/src/lib/components/calendar/CalendarView.svelte +++ b/src/lib/components/calendar/CalendarView.svelte @@ -21,11 +21,24 @@ const NS = 1_000_000; const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const MONTH_NAMES = [ - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December' + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' ]; - $: calColorMap = calendars.reduce((acc, c) => ({ ...acc, [c.id]: c.color }), {} as Record); + $: calColorMap = calendars.reduce( + (acc, c) => ({ ...acc, [c.id]: c.color }), + {} as Record + ); $: filteredEvents = events.filter((e) => visibleCalendarIds.has(e.calendar_id)); // Pre-group events by day key so the template reactively updates when events change @@ -102,7 +115,11 @@ }); } - function getEventsForHour(day: Date, hour: number, eventsList: CalendarEventModel[] = filteredEvents): CalendarEventModel[] { + function getEventsForHour( + day: Date, + hour: number, + eventsList: CalendarEventModel[] = filteredEvents + ): CalendarEventModel[] { const hourStartMs = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour).getTime(); const hourEndMs = hourStartMs + 3_600_000; return eventsList.filter((e) => { @@ -157,9 +174,10 @@ dispatch('eventClick', event); } - $: headerText = view === 'day' - ? `${DAY_NAMES[currentDate.getDay()]}, ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getDate()}, ${currentDate.getFullYear()}` - : `${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`; + $: headerText = + view === 'day' + ? `${DAY_NAMES[currentDate.getDay()]}, ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getDate()}, ${currentDate.getFullYear()}` + : `${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`;
@@ -168,7 +186,10 @@
{#if $mobile}
- + -
@@ -232,7 +285,19 @@ class="md:hidden px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition text-sm flex items-center" on:click={() => dispatch('newEvent')} > - +
@@ -244,13 +309,19 @@
{#each DAY_NAMES as day} -
{$i18n.t(day)}
+
+ {$i18n.t(day)} +
{/each}
-
+
{#each monthDays as day, i} - {@const dayKey = new Date(day.getFullYear(), day.getMonth(), day.getDate()).getTime().toString()} + {@const dayKey = new Date(day.getFullYear(), day.getMonth(), day.getDate()) + .getTime() + .toString()} {@const dayEvents = eventsByDay[dayKey] || []} {@const col = i % 7} {@const row = Math.floor(i / 7)} @@ -293,18 +364,34 @@
- + {:else if view === 'week'}
-
+
-
+
{#each weekDays as day} -
-
{DAY_NAMES[day.getDay()]}
-
+
+
+ {DAY_NAMES[day.getDay()]} +
+
{day.getDate()}
@@ -313,12 +400,22 @@
{#each hours as hour} -
-
{hour > 0 ? formatHour(hour) : ''}
+
+
+ {hour > 0 ? formatHour(hour) : ''} +
{#each weekDays as day} {@const hourEvents = getEventsForHour(day, hour, filteredEvents)}
- + {:else}
-
+
{#each hours as hour} {@const hourEvents = getEventsForHour(currentDate, hour, filteredEvents)} -
-
{hour > 0 ? formatHour(hour) : ''}
+
+
+ {hour > 0 ? formatHour(hour) : ''} +
+ + {/if}
-
{$i18n.t('Settings')}
- + {/if} + + {#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools} +
+ {/if} {#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)} - { - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return; - e.preventDefault(); - show = false; - goto('/automations'); - if ($mobile) { - await tick(); - showSidebar.set(false); - } - }} - > - -
{$i18n.t('Automations')}
- + + + {/if} +
{/if} {#if $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)} - { - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return; - e.preventDefault(); - show = false; - goto('/calendar'); - }} - > - -
{$i18n.t('Calendar')}
- + + + {/if} +
{/if} {#if role === 'admin'} - { - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { - return; - } - e.preventDefault(); - show = false; - goto('/playground'); - if ($mobile) { - await tick(); - showSidebar.set(false); - } - }} - > -
- -
-
{$i18n.t('Playground')}
-
+ {/if} +
+
+ + {#if role === 'admin'} Date: Sun, 19 Apr 2026 23:17:25 +0900 Subject: [PATCH 067/119] refac --- src/routes/(app)/calendar/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(app)/calendar/+page.svelte b/src/routes/(app)/calendar/+page.svelte index 25671fc791..6e7296efb3 100644 --- a/src/routes/(app)/calendar/+page.svelte +++ b/src/routes/(app)/calendar/+page.svelte @@ -159,7 +159,7 @@ {#if loaded}
- -
{#if ($models ?? []).length > 0 && (($settings?.pinnedModels ?? []).length > 0 || $config?.default_pinned_models)} Date: Sun, 19 Apr 2026 23:46:32 +0900 Subject: [PATCH 071/119] refac --- .../components/layout/Sidebar/UserMenu.svelte | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index 05668e25d5..ec86a11a9d 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -234,50 +234,6 @@
{/if} - {#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))} - - {/if} - {#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
{/if} - {#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)} + {#if ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))}
{ if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) return; e.preventDefault(); show = false; - goto('/automations'); + goto('/notes'); if ($mobile) { await tick(); showSidebar.set(false); @@ -353,35 +309,22 @@ }} >
- - - +
-
{$i18n.t('Automations')}
+
{$i18n.t('Notes')}
{#if shiftKey}
{/if} + {#if $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)} + + {/if} + {#if role === 'admin'} - {#if pinnedItems.includes('notes') && ($config?.features?.enable_notes ?? false) && ($user?.role === 'admin' || ($user?.permissions?.features?.notes ?? true))} - - {/if} - - {#if pinnedItems.includes('workspace') && ($user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools)} - - {/if} - - {#if pinnedItems.includes('automations') && $config?.features?.enable_automations && ($user?.role === 'admin' || $user?.permissions?.features?.automations)} - - {/if} - - {#if pinnedItems.includes('calendar') && $config?.features?.enable_calendar && ($user?.role === 'admin' || $user?.permissions?.features?.calendar)} - - {/if} - - {#if pinnedItems.includes('playground') && $user?.role === 'admin'} - - {/if} + {#each pinnedItems as itemId (itemId)} + {@const meta = getMenuItemMeta(itemId)} + {#if meta && isMenuItemVisible(itemId)} + + {/if} + {/each}
From eb16ae92a5b8f93fe3fde9fba709bcfd85792f6d Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 23:49:45 +0900 Subject: [PATCH 073/119] chore: format --- src/lib/components/layout/Sidebar.svelte | 47 ++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 10243df067..fcea714f02 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -856,7 +856,7 @@
- {#each pinnedItems as itemId (itemId)} + {#each pinnedItems as itemId (itemId)} {@const meta = getMenuItemMeta(itemId)} {#if meta && isMenuItemVisible(itemId)}
@@ -877,16 +877,49 @@ {#if itemId === 'notes'} {:else if itemId === 'workspace'} - - + + {:else if itemId === 'automations'} - - + + {:else if itemId === 'calendar'} - - + + {:else if itemId === 'playground'} From f6d1969067269ce3ff12a21ff090f73ddf88b793 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 19 Apr 2026 23:55:17 +0900 Subject: [PATCH 074/119] refac --- .../components/layout/Sidebar/UserMenu.svelte | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/lib/components/layout/Sidebar/UserMenu.svelte b/src/lib/components/layout/Sidebar/UserMenu.svelte index ec86a11a9d..d7b79752c6 100644 --- a/src/lib/components/layout/Sidebar/UserMenu.svelte +++ b/src/lib/components/layout/Sidebar/UserMenu.svelte @@ -234,6 +234,74 @@
{/if} + + + {#if role === 'admin'} + { + if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { + return; + } + e.preventDefault(); + show = false; + goto('/admin'); + if ($mobile) { + await tick(); + showSidebar.set(false); + } + }} + > +
+ +
+
{$i18n.t('Admin Panel')}
+
+ {/if} + + + +
+ {#if $user?.role === 'admin' || $user?.permissions?.workspace?.models || $user?.permissions?.workspace?.knowledge || $user?.permissions?.workspace?.prompts || $user?.permissions?.workspace?.tools}
{/if} -
- - - - - - {#if role === 'admin'} -
{ - if (e.metaKey || e.ctrlKey || e.shiftKey || e.button === 1) { - return; - } - e.preventDefault(); - show = false; - goto('/admin'); - if ($mobile) { - await tick(); - showSidebar.set(false); - } - }} - > -
- -
-
{$i18n.t('Admin Panel')}
-
- {/if} - {#if help}
From 1d501cfa3f96b3a9a5f4f7ce996947671fd09f29 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Apr 2026 00:07:34 +0900 Subject: [PATCH 075/119] refac --- backend/open_webui/models/calendar.py | 60 ++++++--------------------- 1 file changed, 13 insertions(+), 47 deletions(-) diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index 9afa7c15e2..4841e7b2dd 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -248,7 +248,7 @@ class CalendarTable: return CalendarModel.model_validate(cal_data) async def get_or_create_defaults(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]: - """Return user's calendars, creating 'Personal' and 'Scheduled Tasks' if none exist.""" + """Return user's calendars, creating 'Personal' default if none exist.""" async with get_async_db_context(db) as db: result = await db.execute( select(Calendar).filter(Calendar.user_id == user_id).order_by(Calendar.created_at.asc()) @@ -259,29 +259,18 @@ class CalendarTable: return [CalendarModel.model_validate(c) for c in calendars] now = int(time.time_ns()) - defaults = [ - Calendar( - id=str(uuid4()), - user_id=user_id, - name='Personal', - color='#3b82f6', - is_default=True, - created_at=now, - updated_at=now, - ), - Calendar( - id=str(uuid4()), - user_id=user_id, - name='Scheduled Tasks', - color='#8b5cf6', - created_at=now + 1, - updated_at=now + 1, - ), - ] - for cal in defaults: - db.add(cal) + cal = Calendar( + id=str(uuid4()), + user_id=user_id, + name='Personal', + color='#3b82f6', + is_default=True, + created_at=now, + updated_at=now, + ) + db.add(cal) await db.commit() - return [CalendarModel.model_validate(c) for c in defaults] + return [CalendarModel.model_validate(cal)] async def get_calendars_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> list[CalendarModel]: """Owned + shared calendars.""" @@ -317,30 +306,7 @@ class CalendarTable: cal = result.scalars().first() return await self._to_calendar_model(cal, db=db) if cal else None - async def get_scheduled_tasks_calendar( - self, user_id: str, db: Optional[AsyncSession] = None - ) -> Optional[CalendarModel]: - """Get the user's Scheduled Tasks calendar (for automation integration).""" - async with get_async_db_context(db) as db: - result = await db.execute( - select(Calendar).filter( - Calendar.user_id == user_id, - Calendar.name == 'Scheduled Tasks', - ) - ) - cal = result.scalars().first() - if not cal: - # Ensure defaults exist then retry - await self.get_or_create_defaults(user_id, db=db) - result = await db.execute( - select(Calendar).filter( - Calendar.user_id == user_id, - Calendar.name == 'Scheduled Tasks', - ) - ) - cal = result.scalars().first() - # Lightweight return — skip access_grants loading since we only need id/color - return CalendarModel.model_validate(cal) if cal else None + async def insert_new_calendar( self, user_id: str, form_data: CalendarForm, db: Optional[AsyncSession] = None From 24dd5b461eb44d306c823389e0f664c45db042e8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Apr 2026 00:09:24 +0900 Subject: [PATCH 076/119] refac --- backend/open_webui/routers/calendar.py | 51 +++++++++++++++---- .../calendar/CalendarEventModal.svelte | 2 +- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 47093ca788..cde2ea0484 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -30,6 +30,8 @@ log = logging.getLogger(__name__) router = APIRouter() +SCHEDULED_TASKS_CALENDAR_ID = '__scheduled_tasks__' + async def check_calendar_permission(request: Request, user): """Check global feature flag AND per-user permission for calendar access.""" @@ -47,6 +49,17 @@ async def check_calendar_permission(request: Request, user): ) +async def _user_has_automations(request: Request, user) -> bool: + """Check if automations feature is available to this user.""" + if not getattr(request.app.state.config, 'ENABLE_AUTOMATIONS', False): + return False + if user.role == 'admin': + return True + return await has_permission( + user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS + ) + + async def _check_calendar_access(calendar_id: str, user: UserModel, permission: str = 'write') -> CalendarModel: """Verify user has access to a calendar. Returns the calendar or raises 403/404.""" cal = await Calendars.get_calendar_by_id(calendar_id) @@ -74,9 +87,26 @@ async def _check_calendar_access(calendar_id: str, user: UserModel, permission: @router.get('/', response_model=list[CalendarModel]) async def get_calendars(request: Request, user: UserModel = Depends(get_verified_user)): - """List user's calendars (owned + shared). Auto-creates defaults on first call.""" + """List user's calendars (owned + shared), plus a virtual Scheduled Tasks calendar + when automations are available.""" await check_calendar_permission(request, user) - return await Calendars.get_calendars_by_user(user.id) + calendars = await Calendars.get_calendars_by_user(user.id) + + if await _user_has_automations(request, user): + now = int(time.time_ns()) + calendars.append( + CalendarModel( + id=SCHEDULED_TASKS_CALENDAR_ID, + user_id=user.id, + name='Scheduled Tasks', + color='#8b5cf6', + is_default=False, + created_at=now, + updated_at=now, + ) + ) + + return calendars @router.post('/create', response_model=CalendarModel) @@ -144,11 +174,12 @@ async def get_events( expanded.append(event) # 2. Virtual automation events (Scheduled Tasks calendar) - try: - from open_webui.models.automations import Automations, AutomationRuns + if await _user_has_automations(request, user) and ( + cal_id_list is None or SCHEDULED_TASKS_CALENDAR_ID in cal_id_list + ): + try: + from open_webui.models.automations import Automations, AutomationRuns - scheduled_cal = await Calendars.get_scheduled_tasks_calendar(user.id) - if scheduled_cal and (cal_id_list is None or scheduled_cal.id in cal_id_list): # Future runs: expand RRULEs for active automations only active_automations = await Automations.get_active_by_user(user.id) for auto in active_automations: @@ -158,7 +189,7 @@ async def get_events( virtual = { 'id': f'auto_{auto.id}', - 'calendar_id': scheduled_cal.id, + 'calendar_id': SCHEDULED_TASKS_CALENDAR_ID, 'user_id': user.id, 'title': auto.name, 'description': auto.data.get('prompt', '') if auto.data else '', @@ -190,7 +221,7 @@ async def get_events( expanded.append( CalendarEventUserResponse( id=f'run_{run.id}', - calendar_id=scheduled_cal.id, + calendar_id=SCHEDULED_TASKS_CALENDAR_ID, user_id=user.id, title=auto.name, description=run.error if run.status == 'error' else '', @@ -213,8 +244,8 @@ async def get_events( user=None, ) ) - except Exception as e: - log.warning(f'Failed to compute automation events: {e}', exc_info=True) + except Exception as e: + log.warning(f'Failed to compute automation events: {e}', exc_info=True) return [e.model_dump() if hasattr(e, 'model_dump') else e for e in expanded] diff --git a/src/lib/components/calendar/CalendarEventModal.svelte b/src/lib/components/calendar/CalendarEventModal.svelte index ad7bf9b0df..bba3b3426b 100644 --- a/src/lib/components/calendar/CalendarEventModal.svelte +++ b/src/lib/components/calendar/CalendarEventModal.svelte @@ -184,7 +184,7 @@ class="w-full text-sm bg-transparent outline-hidden cursor-pointer" bind:value={calendarId} > - {#each calendars.filter((c) => c.name !== 'Scheduled Tasks') as cal (cal.id)} + {#each calendars.filter((c) => c.id !== '__scheduled_tasks__') as cal (cal.id)} {/each} From 4e31fa4427037c0ffd4ad704308203639bf05df8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 20 Apr 2026 00:12:53 +0900 Subject: [PATCH 077/119] refac --- .../calendar/CalendarSidebar.svelte | 2 +- .../components/calendar/CalendarView.svelte | 164 ---------------- src/routes/(app)/calendar/+page.svelte | 180 ++++++++++++++++-- 3 files changed, 170 insertions(+), 176 deletions(-) diff --git a/src/lib/components/calendar/CalendarSidebar.svelte b/src/lib/components/calendar/CalendarSidebar.svelte index 8ed0df4727..dc98df05e0 100644 --- a/src/lib/components/calendar/CalendarSidebar.svelte +++ b/src/lib/components/calendar/CalendarSidebar.svelte @@ -71,7 +71,7 @@
-
+
{miniMonthNames[miniMonth]} {miniYear}
- -
- {/if} - -
-
- {headerText} - - -
- -
- - - - - -
-
-
- - {#if view === 'month'}
diff --git a/src/routes/(app)/calendar/+page.svelte b/src/routes/(app)/calendar/+page.svelte index 6e7296efb3..2267c1c799 100644 --- a/src/routes/(app)/calendar/+page.svelte +++ b/src/routes/(app)/calendar/+page.svelte @@ -14,6 +14,11 @@ import CalendarEventModal from '$lib/components/calendar/CalendarEventModal.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; import Plus from '$lib/components/icons/Plus.svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; + import SidebarIcon from '$lib/components/icons/Sidebar.svelte'; + import Select from '$lib/components/common/Select.svelte'; + import Check from '$lib/components/icons/Check.svelte'; + import ChevronDown from '$lib/components/icons/ChevronDown.svelte'; const i18n = getContext('i18n'); @@ -29,6 +34,22 @@ let editEvent: CalendarEventModel | null = null; let defaultStartAt: number | null = null; + const MONTH_NAMES = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + function getVisibleRange(): { start: string; end: string } { const d = new Date(currentDate); let start: Date; @@ -128,8 +149,29 @@ showEventModal = true; } + function navigateCalendar(delta: number) { + const d = new Date(currentDate); + if (view === 'month') { + d.setDate(1); + d.setMonth(d.getMonth() + delta); + } else if (view === 'week') d.setDate(d.getDate() + delta * 7); + else d.setDate(d.getDate() + delta); + currentDate = d; + handleNavigate(); + } + + function goToToday() { + currentDate = new Date(); + handleNavigate(); + } + $: defaultCalendarId = calendars.find((c) => c.is_default)?.id || calendars[0]?.id || ''; + $: headerText = + view === 'day' + ? `${DAY_NAMES[currentDate.getDay()]}, ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getDate()}, ${currentDate.getFullYear()}` + : `${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`; + onMount(async () => { await loadCalendars(); await refresh(); @@ -157,17 +199,134 @@ : ''} max-w-full" > {#if loaded} + + +
- From e88e565ab46ed85a7bc95d45ca1057b2951810ed Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:18:54 +0900 Subject: [PATCH 096/119] refac --- backend/open_webui/utils/misc.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index 441f26a918..670a94b512 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -148,22 +148,19 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: messages = [] pending_tool_calls = [] pending_content = [] - pending_reasoning = '' def flush_pending(): - nonlocal pending_content, pending_tool_calls, pending_reasoning - if pending_content or pending_tool_calls or pending_reasoning: + nonlocal pending_content, pending_tool_calls + if pending_content or pending_tool_calls: messages.append( { 'role': 'assistant', 'content': '\n'.join(pending_content) if pending_content else '', **({'tool_calls': pending_tool_calls} if pending_tool_calls else {}), - **({'reasoning_content': pending_reasoning} if pending_reasoning else {}), } ) pending_content = [] pending_tool_calls = [] - pending_reasoning = '' for item in output: item_type = item.get('type', '') @@ -248,10 +245,12 @@ def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]: start_tag = item.get('start_tag', '') end_tag = item.get('end_tag', '') pending_content.append(f'{start_tag}{reasoning_text}{end_tag}') - # Preserve raw reasoning text as reasoning_content for - # providers that require it on assistant tool-call messages - # (e.g. Moonshot/Kimi K2.5). - pending_reasoning += reasoning_text + # NOTE: Some providers (e.g. Moonshot/Kimi K2.5) require + # reasoning_content as a top-level field on assistant + # messages. This should be handled externally via a + # pipeline filter or connection-level middleware, not + # here — adding it universally breaks strict providers + # (OpenAI, Vertex AI, Azure) that reject unknown fields. # else: skip reasoning blocks for normal LLM messages elif item_type == 'open_webui:code_interpreter': From 4790faba73b1fbc00a296529d4b1ced524247cc7 Mon Sep 17 00:00:00 2001 From: G30 <50341825+silentoplayz@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:21:48 -0400 Subject: [PATCH 097/119] fix(ui): add shift+click to bypass message deletion confirmation (#23888) --- src/lib/components/chat/Messages/ResponseMessage.svelte | 8 ++++++-- src/lib/components/chat/Messages/UserMessage.svelte | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 487c3a59ab..2d339c6f36 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -1381,8 +1381,12 @@ class="{isLastMessage || ($settings?.highContrastMode ?? false) ? 'visible' : 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" - on:click={() => { - showDeleteConfirm = true; + on:click={(e) => { + if (e.shiftKey) { + deleteMessageHandler(); + } else { + showDeleteConfirm = true; + } }} > { - showDeleteConfirm = true; + on:click={(e) => { + if (e.shiftKey) { + deleteMessageHandler(); + } else { + showDeleteConfirm = true; + } }} > Date: Tue, 21 Apr 2026 07:29:33 +0300 Subject: [PATCH 098/119] fix: always rAF-throttle markdown parsing during streaming (#23868) --- src/lib/components/chat/Messages/Markdown.svelte | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/lib/components/chat/Messages/Markdown.svelte b/src/lib/components/chat/Messages/Markdown.svelte index 50c50d5725..d0b54b6528 100644 --- a/src/lib/components/chat/Messages/Markdown.svelte +++ b/src/lib/components/chat/Messages/Markdown.svelte @@ -71,17 +71,11 @@ }; const updateHandler = (content) => { - if (content) { - if (done) { - cancelAnimationFrame(pendingUpdate); + if (content && !pendingUpdate) { + pendingUpdate = requestAnimationFrame(() => { pendingUpdate = null; parseTokens(); - } else if (!pendingUpdate) { - pendingUpdate = requestAnimationFrame(() => { - pendingUpdate = null; - parseTokens(); - }); - } + }); } }; From a2875f13c688c60b2f25f2d40e5026a14aa632d0 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:33:33 +0900 Subject: [PATCH 099/119] refac --- backend/open_webui/utils/middleware.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 036dc9bf39..0d1680eb93 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2459,6 +2459,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): tool_ids = form_data.pop('tool_ids', None) terminal_id = form_data.pop('terminal_id', None) files = form_data.pop('files', None) + form_data.pop('folder_id', None) # Caller-provided OpenAI-style tools take precedence over server-side # tool resolution (tool_ids, MCP servers, builtin tools). From 46d73c9dcd4ff7afd6c0efc98fd42f5f18cef555 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:46:39 +0900 Subject: [PATCH 100/119] refac --- backend/open_webui/routers/automations.py | 4 ++-- backend/open_webui/tools/builtin.py | 4 ++-- backend/open_webui/utils/automations.py | 21 ++++++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index ed33c4e8cb..4ff66feb97 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -163,7 +163,7 @@ async def create_new_automation( ): await check_automations_permission(request, user) try: - validate_rrule(form_data.data.rrule) + validate_rrule(form_data.data.rrule, tz=user.timezone) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -213,7 +213,7 @@ async def update_automation_by_id( check_automation_access(automation, user) try: - validate_rrule(form_data.data.rrule) + validate_rrule(form_data.data.rrule, tz=user.timezone) except ValueError as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index 9c1a91abc3..afa3cb63a9 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -2575,7 +2575,7 @@ async def create_automation( # Validate the RRULE try: - validate_rrule(rrule) + validate_rrule(rrule, tz=user.timezone) except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) @@ -2656,7 +2656,7 @@ async def update_automation( # Validate RRULE if changed if rrule is not None: try: - validate_rrule(new_rrule) + validate_rrule(new_rrule, tz=user.timezone if user else None) except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 0c6e4e969a..984c8a0e4e 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -61,13 +61,19 @@ def _parse_rule(s: str): return rrulestr(s, ignoretz=True) -def validate_rrule(s: str) -> None: - """Raise ValueError if the RRULE is malformed or exhausted.""" +def validate_rrule(s: str, tz: str = None) -> None: + """Raise ValueError if the RRULE is malformed or exhausted. + + When *tz* is provided the "now" reference uses the user's local + clock so that near-future schedules are not incorrectly rejected + on servers whose system clock is ahead (e.g. UTC vs US timezones). + """ try: rule = _parse_rule(s) except Exception as e: raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e)) - if rule.after(datetime.now()) is None: + now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + if rule.after(now) is None: raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS) @@ -83,10 +89,15 @@ def next_run_ns(s: str, tz: str = None) -> Optional[int]: def next_n_runs_ns(s: str, n: int = 5, tz: str = None) -> list[int]: - """Compute next N occurrences for UI preview.""" + """Compute next N occurrences for UI preview. + + Uses the user's timezone for the starting "now" so that the + preview matches the user's local clock (same as next_run_ns). + """ rule = _parse_rule(s) result = [] - dt = datetime.now() + now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + dt = now for _ in range(n): dt = rule.after(dt) if not dt: From 65834432a38c483421d41da50ebe981166e59053 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:51:39 +0900 Subject: [PATCH 101/119] refac --- backend/open_webui/utils/automations.py | 33 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 984c8a0e4e..95b931e320 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -45,6 +45,22 @@ CALENDAR_ALERT_LOOKAHEAD_MINUTES = int(os.getenv('CALENDAR_ALERT_LOOKAHEAD_MINUT #################### +def _resolve_tz(tz: str = None) -> Optional[ZoneInfo]: + """Safely resolve a timezone string to ZoneInfo. + + Returns None (→ server-local fallback) when *tz* is empty, None, + or an unrecognised IANA key. Logs a warning on bad keys so + misconfiguration is visible in the server logs. + """ + if not tz: + return None + try: + return ZoneInfo(tz) + except (KeyError, Exception): + log.warning('Unknown timezone %r — falling back to server time', tz) + return None + + def _parse_rule(s: str): """Parse RRULE with clock-aligned DTSTART for sub-daily frequencies. @@ -72,19 +88,21 @@ def validate_rrule(s: str, tz: str = None) -> None: rule = _parse_rule(s) except Exception as e: raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e)) - now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + zi = _resolve_tz(tz) + now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now() if rule.after(now) is None: raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS) def next_run_ns(s: str, tz: str = None) -> Optional[int]: """Next occurrence as epoch nanoseconds, respecting user timezone.""" - now = datetime.now(ZoneInfo(tz)) if tz else datetime.now() + zi = _resolve_tz(tz) + now = datetime.now(zi) if zi else datetime.now() dt = _parse_rule(s).after(now.replace(tzinfo=None)) if dt is None: return None - if tz: - dt = dt.replace(tzinfo=ZoneInfo(tz)) + if zi: + dt = dt.replace(tzinfo=zi) return int(dt.timestamp() * 1_000_000_000) @@ -94,16 +112,17 @@ def next_n_runs_ns(s: str, n: int = 5, tz: str = None) -> list[int]: Uses the user's timezone for the starting "now" so that the preview matches the user's local clock (same as next_run_ns). """ + zi = _resolve_tz(tz) rule = _parse_rule(s) result = [] - now = datetime.now(ZoneInfo(tz)).replace(tzinfo=None) if tz else datetime.now() + now = datetime.now(zi).replace(tzinfo=None) if zi else datetime.now() dt = now for _ in range(n): dt = rule.after(dt) if not dt: break - if tz: - dt_tz = dt.replace(tzinfo=ZoneInfo(tz)) + if zi: + dt_tz = dt.replace(tzinfo=zi) result.append(int(dt_tz.timestamp() * 1_000_000_000)) else: result.append(int(dt.timestamp() * 1_000_000_000)) From f485309fd69816dcd025af00717db2b9d422a7dc Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 13:57:43 +0900 Subject: [PATCH 102/119] refac --- src/app.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app.css b/src/app.css index ac02afcb1d..9352177bd8 100644 --- a/src/app.css +++ b/src/app.css @@ -260,10 +260,15 @@ select { display: none; } -/* Hide leaked Mermaid temp containers if render cleanup misses */ +/* Hide leaked Mermaid temp containers if render cleanup misses. + Use visibility:hidden (not display:none) so mermaid can still + measure the SVG layout before extracting its HTML. */ body > div[id^='dmermaid-'], body > iframe[id^='imermaid-'] { - display: none !important; + position: fixed !important; + visibility: hidden !important; + height: 0 !important; + overflow: hidden !important; } .scrollbar-hidden:active::-webkit-scrollbar-thumb, From a27916d1dbd9bc6890f35acb7228e1f2463a3409 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 14:31:04 +0900 Subject: [PATCH 103/119] refac --- backend/open_webui/functions.py | 17 +++++++++++++++-- backend/open_webui/utils/middleware.py | 23 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/functions.py b/backend/open_webui/functions.py index 8bfc2c2b08..1e032759ea 100644 --- a/backend/open_webui/functions.py +++ b/backend/open_webui/functions.py @@ -234,11 +234,24 @@ async def generate_function_chat_completion(request, form_data, user, models: di oauth_token = None try: - if request.cookies.get('oauth_session_id', None): + oauth_session_id = request.cookies.get('oauth_session_id', None) + if oauth_session_id: oauth_token = await request.app.state.oauth_manager.get_oauth_token( user.id, - request.cookies.get('oauth_session_id', None), + oauth_session_id, ) + + # Fallback: no cookie (automation, API key, etc.) — use most recent session + if oauth_token is None: + from open_webui.models.oauth_sessions import OAuthSessions + + sessions = await OAuthSessions.get_sessions_by_user_id(user.id) + if sessions: + best = max(sessions, key=lambda s: s.updated_at) + oauth_token = await request.app.state.oauth_manager.get_oauth_token( + user.id, + best.id, + ) except Exception as e: log.error(f'Error getting OAuth token: {e}') diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 0d1680eb93..8d3b6dd267 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2916,13 +2916,32 @@ def build_response_object(response, response_data): async def get_system_oauth_token(request, user): + """Get the system OAuth token for a user. + + Primary path: use the oauth_session_id cookie (browser requests). + Fallback: look up the user's most recent OAuth session from the DB + (covers automations, API calls, and other cookie-less contexts). + """ oauth_token = None try: - if request.cookies.get('oauth_session_id', None): + oauth_session_id = request.cookies.get('oauth_session_id', None) + if oauth_session_id: oauth_token = await request.app.state.oauth_manager.get_oauth_token( user.id, - request.cookies.get('oauth_session_id', None), + oauth_session_id, ) + + # Fallback: no cookie (automation, API key, etc.) — use most recent session + if oauth_token is None: + from open_webui.models.oauth_sessions import OAuthSessions + + sessions = await OAuthSessions.get_sessions_by_user_id(user.id) + if sessions: + best = max(sessions, key=lambda s: s.updated_at) + oauth_token = await request.app.state.oauth_manager.get_oauth_token( + user.id, + best.id, + ) except Exception as e: log.error(f'Error getting OAuth token: {e}') return oauth_token From c4aac0415cf89b535edf1700473c50dc22f4fb64 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 14:58:28 +0900 Subject: [PATCH 104/119] refac --- backend/open_webui/internal/db.py | 120 +++++++++++++++++++++++++-- backend/open_webui/migrations/env.py | 5 ++ 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index d1c4060cae..e3b4a110cd 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -1,8 +1,10 @@ import os import json import logging +import ssl as _stdlib_ssl from contextlib import asynccontextmanager, contextmanager from typing import Any, Optional +from urllib.parse import parse_qs, urlencode, urlparse, urlunparse from open_webui.internal.wrappers import register_connection from open_webui.env import ( @@ -35,6 +37,96 @@ from typing_extensions import Self log = logging.getLogger(__name__) +def extract_ssl_mode_from_url(url: str) -> tuple[str, str | None]: + """Strip SSL query-string parameters from a PostgreSQL URL. + + asyncpg and psycopg2 use different query-string keys for SSL + (``ssl`` vs ``sslmode``). This helper removes **both** from the + URL so that each driver can receive the correct parameter through + its own mechanism (query-string re-injection for psycopg2, + ``connect_args`` for asyncpg). + + Returns + ------- + (url_without_ssl, ssl_mode) + *url_without_ssl* is the original URL with ``ssl`` / ``sslmode`` + query parameters removed. *ssl_mode* is the extracted mode + string (e.g. ``'require'``), or ``None`` if neither parameter + was present. + + Non-PostgreSQL URLs are returned unchanged with ``ssl_mode=None``. + """ + if not url or not any( + url.startswith(prefix) + for prefix in ('postgresql://', 'postgresql+', 'postgres://') + ): + return url, None + + parsed = urlparse(url) + query_params = parse_qs(parsed.query, keep_blank_values=True) + + # Prefer sslmode (libpq canonical) over the asyncpg-only ssl key. + ssl_mode: str | None = None + for key in ('sslmode', 'ssl'): + values = query_params.pop(key, None) + if values and ssl_mode is None: + ssl_mode = values[0] + + if ssl_mode is None: + # Nothing to strip — return the URL untouched. + return url, None + + # Rebuild the query string without the SSL keys. + remaining_query = urlencode(query_params, doseq=True) + url_without_ssl = urlunparse(parsed._replace(query=remaining_query)) + return url_without_ssl, ssl_mode + + +def build_asyncpg_ssl_args(ssl_mode: str | None) -> dict: + """Convert a libpq-style SSL mode value to asyncpg ``connect_args``. + + Returns a dict suitable for unpacking into + ``create_async_engine(..., connect_args=...)``. + """ + if ssl_mode is None: + return {} + + mode = ssl_mode.lower() + if mode == 'disable': + return {'connect_args': {'ssl': False}} + if mode in ('allow', 'prefer'): + # asyncpg has no direct equivalent — omit to let it try without. + return {} + if mode == 'require': + # SSL required but no certificate verification (matches libpq). + ctx = _stdlib_ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = _stdlib_ssl.CERT_NONE + return {'connect_args': {'ssl': ctx}} + if mode in ('verify-ca', 'verify-full'): + # Full verification — use the system trust store. + ctx = _stdlib_ssl.create_default_context() + if mode == 'verify-ca': + ctx.check_hostname = False + return {'connect_args': {'ssl': ctx}} + + # Unknown value — pass through as-is and let asyncpg decide. + return {'connect_args': {'ssl': ssl_mode}} + + +def reattach_ssl_mode_to_url(url_without_ssl: str, ssl_mode: str | None) -> str: + """Re-append ``sslmode=`` to a cleaned PostgreSQL URL. + + Used for psycopg2 / libpq consumers that expect the canonical + ``sslmode`` query-string key. + """ + if ssl_mode is None: + return url_without_ssl + separator = '&' if '?' in url_without_ssl else '?' + return f'{url_without_ssl}{separator}sslmode={ssl_mode}' + + + class JSONField(types.TypeDecorator): impl = types.Text cache_ok = True @@ -60,10 +152,14 @@ class JSONField(types.TypeDecorator): # Workaround to handle the peewee migration # This is required to ensure the peewee migration is handled before the alembic migration def handle_peewee_migration(DATABASE_URL): - # db = None + db = None try: + # Normalize SSL params so psycopg2 always sees `sslmode=` (never `ssl=`). + url_without_ssl, ssl_mode = extract_ssl_mode_from_url(DATABASE_URL) + normalized_url = reattach_ssl_mode_to_url(url_without_ssl, ssl_mode) + # Replace the postgresql:// with postgres:// to handle the peewee migration - db = register_connection(DATABASE_URL.replace('postgresql://', 'postgres://')) + db = register_connection(normalized_url.replace('postgresql://', 'postgres://')) migrate_dir = OPEN_WEBUI_DIR / 'internal' / 'migrations' router = Router(db, logger=log, migrate_dir=migrate_dir) router.run() @@ -79,14 +175,20 @@ def handle_peewee_migration(DATABASE_URL): db.close() # Assert if db connection has been closed - assert db.is_closed(), 'Database connection is still open.' + if db is not None: + assert db.is_closed(), 'Database connection is still open.' if ENABLE_DB_MIGRATIONS: handle_peewee_migration(DATABASE_URL) -SQLALCHEMY_DATABASE_URL = DATABASE_URL +# Normalize SSL params from the URL once; each engine branch re-injects +# the driver-appropriate form. +DATABASE_URL_WITHOUT_SSL, DATABASE_SSL_MODE = extract_ssl_mode_from_url(DATABASE_URL) + +# For psycopg2 (sync engine), re-append sslmode=. +SQLALCHEMY_DATABASE_URL = reattach_ssl_mode_to_url(DATABASE_URL_WITHOUT_SSL, DATABASE_SSL_MODE) if DATABASE_SSL_MODE else DATABASE_URL def _make_async_url(url: str) -> str: @@ -229,7 +331,8 @@ get_db = contextmanager(get_session) # ASYNC ENGINE (used for ALL runtime database operations) # ============================================================ -ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url(SQLALCHEMY_DATABASE_URL) +# Use the SSL-stripped URL for asyncpg — SSL is injected via connect_args. +ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url(DATABASE_URL_WITHOUT_SSL if DATABASE_SSL_MODE else SQLALCHEMY_DATABASE_URL) if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL: # Generous default — async coroutines + no session sharing = high connection demand. @@ -251,6 +354,10 @@ if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL: def _set_sqlite_pragmas(dbapi_connection, connection_record): _apply_sqlite_pragmas(dbapi_connection) else: + # Inject asyncpg-compatible SSL connect_args when the user specified + # sslmode/ssl in DATABASE_URL. + asyncpg_ssl_args = build_asyncpg_ssl_args(DATABASE_SSL_MODE) + if isinstance(DATABASE_POOL_SIZE, int): if DATABASE_POOL_SIZE > 0: async_engine = create_async_engine( @@ -260,17 +367,20 @@ else: pool_timeout=DATABASE_POOL_TIMEOUT, pool_recycle=DATABASE_POOL_RECYCLE, pool_pre_ping=True, + **asyncpg_ssl_args, ) else: async_engine = create_async_engine( ASYNC_SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, poolclass=NullPool, + **asyncpg_ssl_args, ) else: async_engine = create_async_engine( ASYNC_SQLALCHEMY_DATABASE_URL, pool_pre_ping=True, + **asyncpg_ssl_args, ) diff --git a/backend/open_webui/migrations/env.py b/backend/open_webui/migrations/env.py index 3840cb4a17..f5e57920ea 100644 --- a/backend/open_webui/migrations/env.py +++ b/backend/open_webui/migrations/env.py @@ -5,6 +5,7 @@ from alembic import context from open_webui.models.auths import Auth from open_webui.models.calendar import Calendar, CalendarEvent, CalendarEventAttendee # noqa: F401 from open_webui.env import DATABASE_URL, DATABASE_PASSWORD, LOG_FORMAT +from open_webui.internal.db import extract_ssl_mode_from_url, reattach_ssl_mode_to_url from sqlalchemy import engine_from_config, pool, create_engine # this is the Alembic Config object, which provides @@ -36,6 +37,10 @@ target_metadata = Auth.metadata DB_URL = DATABASE_URL +# Normalize SSL query params for psycopg2 (Alembic uses psycopg2, not asyncpg). +url_without_ssl, ssl_mode = extract_ssl_mode_from_url(DB_URL) +DB_URL = reattach_ssl_mode_to_url(url_without_ssl, ssl_mode) if ssl_mode else DB_URL + if DB_URL: config.set_main_option('sqlalchemy.url', DB_URL.replace('%', '%%')) From 7fd94b0e73b87fcbd5f8f37898bf617c762a6ace Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 15:15:00 +0900 Subject: [PATCH 105/119] refac --- src/lib/components/chat/MessageInput.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 92de0c3d43..aeb96af5b0 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1941,7 +1941,8 @@ {#if !history?.currentId || history.messages[history.currentId]?.done == true} - {#if terminalCapableModels.length > 0 && (($terminalServers ?? []).length > 0 || ($settings?.terminalServers ?? []).some((s) => s.url))} + {@const hasDirectToolServerAccess = $_user?.role === 'admin' || ($_user?.permissions?.features?.direct_tool_servers ?? true)} + {#if terminalCapableModels.length > 0 && (($terminalServers ?? []).some((t) => t.id) || (hasDirectToolServerAccess && (($terminalServers ?? []).some((t) => !t.id) || ($settings?.terminalServers ?? []).some((s) => s.url))))} {/if} From 0e3135f8dc203f94f5fe30e94039a7977b2b2059 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:18:33 +0200 Subject: [PATCH 106/119] chore: changelog (#23187) * chore: add changelog entry for v0.8.13 * changelog: task management, admin model deletion * changelog: emoji, shortcode, input * changelog: swipe-to-reply mobile gesture * changelog: emoji, recently-used, picker * changelog: files, chat-input, attachments * changelog: terminal session tracking, task list visibility * changelog: move terminal session tracking to Added section * changelog: performance, shared chat deletion * changelog: user activity tracking, shared chat deletion optimizations * changelog: add Russian translation entry * changelog: MCP tool server timeout configuration * changelog: image viewer memory optimization * changelog: error message persistence during streaming * changelog: codespan, animation, streaming * changelog: streaming, performance, yield * changelog: text, animation, streaming * changelog: websearch, settings, fix * changelog: automation, scheduling, workflows * changelog: automations, permissions, access * changelog: automations, editor, logs * changelog: german, completion, tokens * changelog: streaming, entities, defaults * changelog: pyodide, cache, prompt * changelog: details, expansion, settings * changelog: unread, sidebar, automations * changelog: oauth, gravatar, prompts * changelog: wake-lock, writing, retrieval * changelog: mcp, sidebar, usage * changelog: oauth, citations, sidebar * changelog: oauth, cookies, tools * changelog: translations, tamil, localization * changelog: tasks, fallback, stability * changelog: title, query, performance * changelog: sidebar, archived, menu * changelog: input, drafts, uploads * changelog: notes, permissions, security * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog date * reorder changelog entries * restore changelog ordering * restore changelog * changelog updates * adjust changelog ordering * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * Update CHANGELOG.md * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * Update CHANGELOG.md * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog * changelog --- CHANGELOG.md | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 126b19e028..47f6a27199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,241 @@ 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.9.0] - 2026-04-20 + +### Added + +- 🖥️ **Native desktop app availability.** Open WebUI is now available as a cross-platform desktop app with local model support, multi-server switching, and offline-ready usage after first launch. [#8262](https://github.com/open-webui/open-webui/issues/8262), [Desktop](https://github.com/open-webui/desktop) +- 🤖 **Scheduled chat automations.** Users can now create, schedule, run, and manage recurring automations from both the dedicated automations page and built-in chat tools, with execution logs, direct run controls, and permission-aware access control for user and group policies. [#23303](https://github.com/open-webui/open-webui/pull/23303), [Commit](https://github.com/open-webui/open-webui/commit/5a2ff8b2e5b6f55a20f7ed491f818490eb535ea7), [Commit](https://github.com/open-webui/open-webui/commit/d30a0531d4add045c21a2368d6321a9b1906865f), [Commit](https://github.com/open-webui/open-webui/commit/bae5ff938ac88a3a647cc31ca8db1101015ae18b), [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) +- 🧰 **Automation tools in chat.** Built-in chat tools can now create, update, list, pause, and delete scheduled automations directly in conversation when automation access is enabled. [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) +- 🤖 **Automation model selection reliability.** Automations created from chat now consistently use the calling model, avoiding mismatches when tool calls run under different model contexts. [Commit](https://github.com/open-webui/open-webui/commit/e709d6812f7fba246c4b7907f9fa41f751717566), [Commit](https://github.com/open-webui/open-webui/commit/398718d5059ce2a5614e9e124f20ef48b843ce42), [#23812](https://github.com/open-webui/open-webui/pull/23812) +- ⏱️ **Automation scheduling limits.** Administrators can now set "AUTOMATION_MAX_COUNT" and "AUTOMATION_MIN_INTERVAL" to limit how many automations each non-admin user can create and prevent overly frequent schedules that could overload the system. [Commit](https://github.com/open-webui/open-webui/commit/406251c2f358ffabce4d631c98c6f2c879feae5c) +- 🧭 **Global automations toggle.** Administrators can now disable automations system-wide with the "ENABLE_AUTOMATIONS" setting, which hides automation pages and tools and pauses background automation processing until it is re-enabled. [Commit](https://github.com/open-webui/open-webui/commit/42694c7c0cc8ba586c1dd364ecfaa0b4080b6cad) +- 📋 **Task management tool.** AI models can now create, update, and track tasks within a chat conversation, breaking down complex requests into manageable steps with real-time status updates. [Commit](https://github.com/open-webui/open-webui/commit/bcb71bb5206ac01d97a39fde8ecf0e0541dde636) +- 🗓️ **Calendar workspace and event management.** Users can now manage personal and shared calendars from a dedicated Calendar page, create and edit events (including recurring events), and view scheduled automations directly alongside calendar activity. [#23880](https://github.com/open-webui/open-webui/pull/23880) +- 🔐 **Calendar permission controls.** Administrators can now control calendar access through feature permissions, so calendar pages, APIs, and built-in calendar tools are available only to users and groups with calendar access enabled. [Commit](https://github.com/open-webui/open-webui/commit/5afc258c5b13f456be528420513ade546c5e86f9), [Commit](https://github.com/open-webui/open-webui/commit/37eba1c5a66b3145c122a6b40e5c29707526d121) +- 🗑️ **Calendar deletion controls.** Calendar sidebar entries now include a delete action with confirmation, allowing users to remove custom calendars directly from the Calendar page. [Commit](https://github.com/open-webui/open-webui/commit/51627555bf356c8ec663f4d2f43f2f013eadbce4) +- 🔔 **Calendar reminders and alerts.** Calendar events now support reminder options from no alert up to one hour before start time, with upcoming alerts delivered through in-app toasts, browser notifications, and optional webhooks while avoiding duplicate sends. [Commit](https://github.com/open-webui/open-webui/commit/e5b5a174265d6710e986f6534ee7e3b2923233be) +- ⚙️ **Scheduler reminder configuration.** Administrators can now configure calendar reminder processing with "SCHEDULER_POLL_INTERVAL" and "CALENDAR_ALERT_LOOKAHEAD_MINUTES", while existing "AUTOMATION_POLL_INTERVAL" setups continue to work as a legacy fallback. [Commit](https://github.com/open-webui/open-webui/commit/e5b5a174265d6710e986f6534ee7e3b2923233be) +- 🗓️ **Unified calendar header controls.** The Calendar page now uses a single top navigation bar for date navigation, view selection, and quick event creation, with improved mobile behavior and label truncation for tighter screens. [Commit](https://github.com/open-webui/open-webui/commit/4e31fa4427037c0ffd4ad704308203639bf05df8), [Commit](https://github.com/open-webui/open-webui/commit/3e3f138d9323987a41b1e3c17721a0047cf8e40f) +- 🧰 **Dedicated task checklist tools.** Built-in task tracking exposes separate tools for creating task lists and updating individual task statuses, giving multi-step chats clearer progress control. [Commit](https://github.com/open-webui/open-webui/commit/a35926261646f8897ba71da1572ed5dff802e3be) +- ☁️ **Azure responses support.** Azure OpenAI connections now support the newer "/openai/v1" format, enabling chat, responses, and proxy calls to work correctly with that endpoint style. [#23484](https://github.com/open-webui/open-webui/pull/23484) +- 🤖 **Ollama responses support.** The Ollama proxy now supports the Responses API, letting clients use "/v1/responses" directly with Ollama-hosted models through Open WebUI. [#23483](https://github.com/open-webui/open-webui/pull/23483) +- 🧩 **Responses tool output rendering.** Built-in tool outputs in Responses API flows now render more consistently so downstream chat output is easier to interpret. [Commit](https://github.com/open-webui/open-webui/commit/e695d854f2d11fada84d5fbec8d3edea4e468e19), [#23482](https://github.com/open-webui/open-webui/pull/23482) +- 🔎 **Responses citation visibility.** Responses API flows now emit citation sources more consistently, making linked references easier to preserve and display in chat output. [Commit](https://github.com/open-webui/open-webui/commit/e695d854f2d11fada84d5fbec8d3edea4e468e19), [#23774](https://github.com/open-webui/open-webui/issues/23774) +- 📎 **Attach previously uploaded files.** The chat input menu now includes a Files tab for browsing and attaching previously uploaded files, eliminating the need to re-upload files you have already shared. [Commit](https://github.com/open-webui/open-webui/commit/edb8971c7dbd974322c3207c4655ff66479c3ee2) +- 🖥️ **Terminal session tracking.** Open Terminal now tracks the current working directory per chat session, so relative paths and navigation work correctly across multiple interactions. [Commit](https://github.com/open-webui/open-webui/commit/a06685a47b89fb19dd6124fbe391ff78b54f451d), [Commit](https://github.com/open-webui/open-webui/commit/6512e085c4e56897dd49e56aff5d616820a962f3) +- 🧷 **Default model terminal selection.** Workspace model editors can now preselect an Open Terminal connection, so new chats automatically start with the model’s configured terminal ready to use. [Commit](https://github.com/open-webui/open-webui/commit/47d413ce7b2a006a8126f4a9055b13e5fcb33a1d), [#23605](https://github.com/open-webui/open-webui/issues/23605) +- 🎙️ **Mistral TTS support.** Mistral can now be used as a text-to-speech provider, with admin settings for the API key, base URL, voices, and model selection. [Commit](https://github.com/open-webui/open-webui/commit/4cee67e2be0c80a0b501073ea49a80d13efd1c41) +- 🎧 **STT preprocessing bypass option.** Administrators can now enable "AUDIO_STT_SKIP_PREPROCESSING" to send audio files directly to the speech-to-text backend, reducing memory and CPU consumption during large uploads for better transcription performance and stability on constrained deployments. [#23661](https://github.com/open-webui/open-webui/pull/23661) +- 🗑️ **Admin model deletion.** Administrators can now delete Ollama models directly from the model selector menu, making it easier to clean up unused or unwanted models. [Commit](https://github.com/open-webui/open-webui/commit/2388dd7dc3530b5dd5419c5d0bb1bcdcb7544099) +- 🔌 **Backend outlet filters for local and persisted chats.** Pipeline and function outlet filters now run reliably in backend completion flows for persisted chats and temporary local chats. [#3237](https://github.com/open-webui/open-webui/issues/3237), [Commit](https://github.com/open-webui/open-webui/commit/cf4218e688def6f11d195aeda6665ae5b5376b67) +- 🎨 **Emoji shortcode support.** Typing a colon in the chat input now opens an emoji suggestion menu, making it easier to insert emojis using shortcodes like :wave:. [Commit](https://github.com/open-webui/open-webui/commit/2040095050056d01c61aa597c5010445449a42c7) +- 📌 **Recently used emojis.** The emoji picker now shows your most recently used emojis at the top, making it faster to find emojis you use often. [Commit](https://github.com/open-webui/open-webui/commit/64da99a32218171d41b3af5acc14783de8dbdf49) +- 👆 **Swipe to reply on mobile.** Swiping right on a message now triggers a reply, making it easier to respond on touch devices with a natural gesture. [Commit](https://github.com/open-webui/open-webui/commit/012ce95f27d57bea8911bd63bfb923443c5797ae) +- 📱 **Screen-awake voice recording.** Voice recording now keeps the screen awake during active dictation and safely re-acquires wake lock after visibility changes, helping prevent long transcriptions from being cut off on mobile devices. [#23145](https://github.com/open-webui/open-webui/issues/23145) +- ✨ **Improved task list visibility.** The task list automatically hides once all tasks are complete and generation is finished, keeping the chat interface cleaner. [Commit](https://github.com/open-webui/open-webui/commit/0ad397c0482004173d4a8bf4722100acc43db454), [Commit](https://github.com/open-webui/open-webui/commit/4b35d70078a2d7a322566699a43594b3c10b2dda) +- 🔔 **Unread chat indicators.** Sidebar chats now show unread status and are marked as read when opened, making it easier to spot conversations with new activity. [Commit](https://github.com/open-webui/open-webui/commit/0638b9f56ce1ba8a496d0e84da2e7fa178b01a3f) +- 🔌 **WebSocket reconnect status feedback.** Open WebUI now warns when the real-time connection drops and confirms when it reconnects, while avoiding a reconnect message on the initial page load. [Commit](https://github.com/open-webui/open-webui/commit/1824e69a70e756cfcf543a9fbe4b0780d9b57292) +- 📍 **Pinned notes in sidebar.** Notes can now be pinned to the sidebar for quick access, and you can also create a new note directly from the pinned notes section. [Commit](https://github.com/open-webui/open-webui/commit/ecd74f220c7dd671d5705189a3f4493a3868c8bf), [Commit](https://github.com/open-webui/open-webui/commit/f1be85d997439b49fc143d2bcd2dc710f44446c8) +- 🗂️ **Model selector focus.** The model selector now resets its search only when it opens, making the popup feel more predictable while still focusing the search field automatically. [Commit](https://github.com/open-webui/open-webui/commit/b89019a8e1f96e01dc8e19a81ef8fb4f4eae3eef) +- 🗂️ **Model selector layout.** The model selector now behaves more predictably as a custom popup, and the completions playground uses a simpler model picker for easier selection. [Commit](https://github.com/open-webui/open-webui/commit/c40ea7f29d34fa9535cdf9ffe599f4429ff3f455) +- 🎚️ **Active filter valve shortcut.** Active filter badges now expose valve configuration directly in the chat input area, so filter tuning is faster during conversations. [Commit](https://github.com/open-webui/open-webui/commit/3c22afc5a67404047797921185aca984b10b45cd), [#23811](https://github.com/open-webui/open-webui/issues/23811), [#23813](https://github.com/open-webui/open-webui/pull/23813) +- 🎨 **Theme updates.** Other windows can now update the app theme directly, keeping the interface in sync when theme changes are triggered externally. [Commit](https://github.com/open-webui/open-webui/commit/9f1b279e88bd22dfff4d2531209536dea6a2f65e) +- 🚀 **Async performance and responsiveness improvements.** The core backend database and request paths now run asynchronously across the application, massively improving responsiveness and performance under concurrent load and reducing request blocking during heavy activity. [Commit](https://github.com/open-webui/open-webui/commit/27169124f220e5cea21c88601c731c3749496ab0), [Commit](https://github.com/open-webui/open-webui/commit/8936721414a17832852a90f3ee592af5a8b7232d) +- ⚡ **Drawer performance and memory optimization.** Drawer interactions now stay smoother over long sessions by removing stale keyboard listeners on teardown, which reduces memory growth and avoids accumulated event handling overhead. [#23724](https://github.com/open-webui/open-webui/pull/23724#issuecomment-4245840810) +- 🚀 **Chat history memory culling.** Long conversations now stay much more responsive by rendering a smaller message window and unloading off-screen messages with spacer-based virtualization, significantly reducing memory pressure and UI freezing on heavy chats and mobile devices. [#23067](https://github.com/open-webui/open-webui/issues/23067), [Commit](https://github.com/open-webui/open-webui/commit/026903399be73ac4b6c226647110e5662d043a50), [Commit](https://github.com/open-webui/open-webui/commit/9dccd29c94875e6f0ac373c5802cb183296e47ff) +- 🧵 **Async file and knowledge processing performance.** File processing, knowledge reindexing, and channel message helper paths now consistently await async operations, preventing skipped processing steps and improving reliability and performance of indexing and tool responses. [Commit](https://github.com/open-webui/open-webui/commit/de27a121511a31606f250ba4033490797216a0eb) +- 🚀 **Persistent chat payload efficiency.** Persisted chats now use server-side history loading instead of repeatedly resending full message payloads, improving multimodal performance and reducing stale-history overwrite risk across devices. [#19064](https://github.com/open-webui/open-webui/issues/19064), [Commit](https://github.com/open-webui/open-webui/commit/18fe17127a7175579506e7456d3e5aba201371e6), [Commit](https://github.com/open-webui/open-webui/commit/cf4218e688def6f11d195aeda6665ae5b5376b67) +- 🧵 **Non-blocking file storage operations.** Uploading, reading, transcribing, and deleting files now offloads storage I/O to background threads, keeping the application responsive during file-heavy workflows. [Commit](https://github.com/open-webui/open-webui/commit/4866bec0f238198a721c952fe18dd04ba643be33) +- 🏃 **Faster automation list loading.** The automations page now loads more smoothly by batching latest-run lookups and avoiding duplicate initial fetches. [Commit](https://github.com/open-webui/open-webui/commit/09f6d7ba57d2aaad83ad0d29d005feb7157776a1) +- 🏎️ **Streaming response performance.** Streaming responses now process each output line in a single step instead of two separate yields, reducing async overhead and improving responsiveness during long-running generations. [#23266](https://github.com/open-webui/open-webui/pull/23266) +- 🔎 **Faster mention parsing.** Chat text with HTML-like content, file paths, or tool output now parses mentions more efficiently, which helps keep typing and rendering responsive in messages that contain many '<' characters. [#23551](https://github.com/open-webui/open-webui/pull/23551) +- 🧪 **Code block rendering performance.** Code blocks now reuse a shared HTML unescape helper, reducing extra browser work when displaying encoded output in chat. [#23553](https://github.com/open-webui/open-webui/pull/23553) +- 🚀 **Inline code rendering performance.** Inline code tokens in streaming responses now fade in with a lightweight CSS animation, making chat output feel smoother while reducing interface overhead during rapid token updates. [#23258](https://github.com/open-webui/open-webui/pull/23258) +- 🎞️ **Streaming text token animation performance.** Streaming text tokens now use a lightweight CSS intro animation, making output feel smoother while reducing transition overhead and preventing tokens from fading out when generation completes. [#23257](https://github.com/open-webui/open-webui/pull/23257) +- 🎯 **Template token scan optimization.** Streaming responses now skip unnecessary token-replacement processing when no template markers are present, reducing per-update overhead and keeping chat output smoother during rapid generation. [#23161](https://github.com/open-webui/open-webui/pull/23161) +- 🔬 **Chinese text processing guard performance.** Streaming responses without Chinese characters now skip unnecessary Chinese-format processing checks, reducing per-update overhead and keeping output smoother during rapid generation. [#23162](https://github.com/open-webui/open-webui/pull/23162) +- 🧠 **HTML entity decode performance.** Streaming text decoding now avoids repeated document parsing for HTML entity handling, reducing memory churn and improving responsiveness in token-heavy chat output. [#23165](https://github.com/open-webui/open-webui/pull/23165) +- 🏷️ **Chat title update performance.** Chat title updates now run in a single database operation instead of multiple round trips, improving responsiveness and reducing overhead when titles are generated or renamed. [#23214](https://github.com/open-webui/open-webui/pull/23214) +- 📂 **Faster chat list queries performance.** Chat and folder lists now load more efficiently by fetching only the fields needed for sidebar views, improving responsiveness when browsing large conversation histories. [Commit](https://github.com/open-webui/open-webui/commit/0e5696de74cc0ba55b24cfc3d02efa83f08d7d3f) +- 📈 **Sidebar memory optimization.** Sidebar chat items now use shared drag-preview resources and safer listener cleanup, reducing memory growth and keeping large chat lists more responsive during long sessions. [#23209](https://github.com/open-webui/open-webui/pull/23209) +- 🧠 **Image viewer memory optimization.** Viewing images and SVGs now uses significantly less memory and performs faster, keeping the application snappy and responsive even when browsing through many media files during extended sessions. [#23236](https://github.com/open-webui/open-webui/pull/23236) +- 📡 **Optimized user activity tracking performance.** User activity updates now use a single database query instead of multiple operations, improving response times across all authenticated requests. [#23215](https://github.com/open-webui/open-webui/pull/23215) +- 👥 **Faster channel thread author loading.** Channel thread responses now load author details in a single batch query, reducing database overhead and improving responsiveness in threads with many participants. [#23795](https://github.com/open-webui/open-webui/pull/23795) +- 💨 **Optimized shared chat deletion.** Deleting shared chats by user is now faster and more memory-efficient by only loading necessary data. [#23216](https://github.com/open-webui/open-webui/pull/23216) +- 🗃️ **Faster chat tag loading.** Chat tag lookups now load only the metadata needed instead of full chat payloads, improving responsiveness for chats with large histories. [#23798](https://github.com/open-webui/open-webui/pull/23798) +- 📎 **Faster chat file deduplication.** Attaching files to chat messages now checks duplicates more efficiently, reducing overhead when handling larger file lists. [#23800](https://github.com/open-webui/open-webui/pull/23800) +- 📈 **Faster message diff checks.** Chat message and status updates now compare content more efficiently during streaming, making active conversations feel smoother and more responsive. [#23370](https://github.com/open-webui/open-webui/pull/23370) +- ⚖️ **Faster deep equality checks.** Chat message updates, model selection, note editing, code block refreshes, and rich text state comparisons now use deep equality checks that reduce unnecessary UI work and improve responsiveness in active sessions. [#23845](https://github.com/open-webui/open-webui/pull/23845) +- 🏃 **Faster knowledge access updates.** Updating access grants for knowledge items now completes with less backend overhead, making permission changes apply more quickly. [#23799](https://github.com/open-webui/open-webui/pull/23799) +- 🧹 **Mermaid render cleanup performance.** Mermaid diagrams now always clean up temporary render elements after failures, reducing DOM buildup and keeping repeated rendering more stable over time. [#23727](https://github.com/open-webui/open-webui/pull/23727) +- 🖼️ **Model image lookup efficiency.** Model profile image requests now reuse the current request database session, reducing per-request overhead and improving response efficiency. [#23796](https://github.com/open-webui/open-webui/pull/23796) +- 👤 **User endpoint query reduction.** Session-based user settings and status endpoints now avoid redundant user re-fetches, reducing unnecessary database load while preserving behavior. [#23794](https://github.com/open-webui/open-webui/pull/23794) +- 🚦 **Faster startup performance.** Open WebUI now checks for Torch MPS support only on macOS, avoiding unnecessary startup work on other platforms. [#23438](https://github.com/open-webui/open-webui/pull/23438) +- 🛡️ **Redis timeout consistency.** Redis connections now honor the "REDIS_SOCKET_CONNECT_TIMEOUT" setting across standard and cluster setups, helping workers fail faster when Redis is unreachable. [#23572](https://github.com/open-webui/open-webui/pull/23572) +- 🧰 **AIOHTTP pool controls.** Administrators can now tune shared outbound HTTP connection behavior with "AIOHTTP_POOL_CONNECTIONS", "AIOHTTP_POOL_CONNECTIONS_PER_HOST", and "AIOHTTP_POOL_DNS_TTL" for better control under high concurrency. [Commit](https://github.com/open-webui/open-webui/commit/c47dd7b7717c4186e0f0549ca3c8cb4d9bb38135) +- ⏱️ **MCP tool server timeout configuration.** Administrators can now configure request timeouts for MCP tool server connections via the AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER environment variable. [Commit](https://github.com/open-webui/open-webui/commit/10b4b86ada93cd62d994c3179ff14dfd1a6e56f0) +- 🎫 **Static OAuth tool authentication.** Tool server authentication now works reliably for both "oauth_2.1" and "oauth_2.1_static" connection types, so OAuth-backed tool access is correctly detected and forwarded during chat requests. [Commit](https://github.com/open-webui/open-webui/commit/60676bfdcfbce1a69b3e97f2013f0cfd63371737) +- 🗄️ **Configurable storage local cache.** Administrators can now disable persistent local caching for cloud-backed uploads with the "STORAGE_LOCAL_CACHE" setting, reducing local disk usage by cleaning temporary upload copies after processing. [Commit](https://github.com/open-webui/open-webui/commit/8172c7e3d56918d1372be06b9369b58a3a88f6b1) +- 🚪 **Back-channel logout.** OpenID Connect providers can now trigger centralized logout through the "ENABLE_OAUTH_BACKCHANNEL_LOGOUT" setting, helping administrators invalidate user sessions more reliably across connected devices. [Commit](https://github.com/open-webui/open-webui/commit/0dd9f462ffb2f160bc4aebad182047f41874d250) +- 🛡️ **Expanded security header controls.** Administrators can now configure additional browser security headers, including "CONTENT_SECURITY_POLICY_REPORT_ONLY", "CROSS_ORIGIN_EMBEDDER_POLICY", "CROSS_ORIGIN_OPENER_POLICY", and "CROSS_ORIGIN_RESOURCE_POLICY", for stricter and more flexible deployment hardening. [Commit](https://github.com/open-webui/open-webui/commit/f246a66810fa4995d9494da3599c0fb297fb0213) +- 🖼️ **Image MIME fallback option.** Administrators can now enable "ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK" so image-to-base64 conversion can still detect common image types by file extension when MIME metadata is missing, improving compatibility on minimal container images and older file records. [Commit](https://github.com/open-webui/open-webui/commit/5127354b3eb4eaa71bc4ad68da69729e2196e7a4) +- 🛡️ **Public sharing permissions.** Public channels, models, notes, prompts, and tools now respect allowed access grants more consistently, helping administrators control who can share content more safely. [Commit](https://github.com/open-webui/open-webui/commit/9d3e0637c86292b8b92e7607097a83f1075d7cd8) +- 🆔 **Skill lookup by ID.** Skill instructions now include each skill’s ID, and the skill viewer now finds skills by ID in a case-insensitive way so attached skills are identified more reliably in chats. [Commit](https://github.com/open-webui/open-webui/commit/65ee771fd0d62d785ecbcf189e3f5b63858c11e6) +- 🏷️ **Source context metadata.** Retrieval source context now includes each source’s resource type and resource ID metadata, helping downstream model workflows preserve richer source identity during processing. [Commit](https://github.com/open-webui/open-webui/commit/c3c8c605d76a3b0ee067307f9cef6d081658e287) +- 🗂️ **Feedback filtering.** Administrators can now filter feedback history by model and export only the feedback they need. [Commit](https://github.com/open-webui/open-webui/commit/60e4d7517463690b3a87de38babc9ac561897c61) +- 📤 **CSV feedback export.** Feedback history can now be exported as either JSON or CSV, making it easier to analyze feedback in spreadsheet tools. [Commit](https://github.com/open-webui/open-webui/commit/342582676a5212bf196a69d11825cb407992f257) +- 📝 **Optional GET audit logging.** Administrators can now enable auditing for GET requests with the "ENABLE_AUDIT_GET_REQUESTS" setting when they need fuller request visibility. [Commit](https://github.com/open-webui/open-webui/commit/5ee791d5d28f236755243cb7d16d8737bb69ce36) +- 🕒 **Model access updates.** Changing a model’s access grants now updates its timestamp, so recently modified models stay easier to find and sort correctly. [Commit](https://github.com/open-webui/open-webui/commit/53eadb7df7281f5661cbe22c8b26b5aedaba3083) +- 💬 **Queued message handling.** Queued chat messages now send more reliably without advancing the queue too early, keeping follow-up prompts in the intended order. [Commit](https://github.com/open-webui/open-webui/commit/730e52a431d157dc62d72260668087437f1d52f4) +- 🔒 **Rendered content safety.** Placeholder descriptions and the pending account notice now render markdown with safer sanitization ordering, reducing the risk of unsafe HTML appearing in these views. [Commit](https://github.com/open-webui/open-webui/commit/253f416de3f2d3a939a6feef2a56413fd61cc70b) +- 🛡️ **Safer placeholder rendering.** Chat placeholder descriptions and the pending account notice now sanitize rendered markdown more consistently, reducing the risk of unsafe content being shown in these views. [Commit](https://github.com/open-webui/open-webui/commit/ae0316a30e01a2e5ff3f9d2f9f759c1cd6410f34) +- 🧮 **Usage analytics accuracy.** Token usage is now normalized before chat messages are saved, so model and user usage reports stay accurate across OpenAI-compatible providers. [Commit](https://github.com/open-webui/open-webui/commit/4dea4fdf54e00ebaba8e3178128bf8709453d2a2) +- 🧩 **Richer Anthropic tool results.** Anthropic-compatible tool calls now preserve more tool result content types, including images and structured search or document outputs, so models can use fuller tool context instead of receiving only plain text fragments. [#23188](https://github.com/open-webui/open-webui/issues/23188), [Commit](https://github.com/open-webui/open-webui/commit/40f5b3d135190dc9a2d8e94dbb1b2cbcbd829132) +- 🖼️ **ComfyUI request reliability.** ComfyUI image generation and editing now use shared async connections with consistent SSL handling, making image uploads and workflow runs more reliable under concurrent load. [Commit](https://github.com/open-webui/open-webui/commit/5944eda0ff25a284f7157252683bccede741cbe7) +- 🎛️ **Reranking batch size control.** Administrators can now set "RAG_RERANKING_BATCH_SIZE" in Documents settings to control reranking workload size, helping balance retrieval speed and resource usage for their deployment. [Commit](https://github.com/open-webui/open-webui/commit/4d2f18981051205016bd24d39521e25a33581225) +- 🔄 **General improvements.** Various improvements were implemented across the application to enhance performance, stability, and security. +- 🌐 **Translation updates.** Translations for Irish, Catalan, German, Simplified Chinese, Hindi, and Portuguese (Brazil) were enhanced and expanded. + +### Fixed + +- 🛡️ **Model description XSS protection.** Model descriptions shown in chat placeholders are now sanitized before rendering, preventing malicious links from executing scripts and helping protect user sessions from takeover. [#23621](https://github.com/open-webui/open-webui/pull/23621) +- 🧠 **Memory search filtering.** Memory search now correctly filters by the query text instead of returning unrelated results. [Commit](https://github.com/open-webui/open-webui/commit/43e5905c133049036353978704b0abd179716749), [#23826](https://github.com/open-webui/open-webui/issues/23826) +- 📊 **Shared chat analytics consistency.** Usage and message-count analytics now count assistant activity consistently across regular and shared chats, improving accuracy in model, user, chat, and time-based reporting views. [Commit](https://github.com/open-webui/open-webui/commit/e29d145a1cff23122de16123a4cfda1b84abffbb) +- 🧭 **Safer in-flight chat navigation.** Sending a message no longer overwrites your active chat or causes duplicate background notifications when you switch conversations before a response finishes. [Commit](https://github.com/open-webui/open-webui/commit/dc6df52a917b49fa1264ac81a8cc74603f6155b3) +- 🗣️ **Pipeline error detail visibility.** Pipeline inlet and outlet failures now preserve and surface provider error details more reliably in chat error messages, making troubleshooting failed requests much clearer. [Commit](https://github.com/open-webui/open-webui/commit/d5e69f182cd7a6371ab25248f6432b277f83ef23) +- 📨 **Shared chat event routing.** Message update and send events now target the chat owner’s event channel, so shared chats receive the correct real-time updates instead of routing events to the acting user. [Commit](https://github.com/open-webui/open-webui/commit/47329b5032ba29716a7e7e973b07c6d9894968e0) +- 🔐 **Consistent outbound SSL handling.** External requests for tools, functions, terminals, webhooks, retrieval loaders, audio provider discovery, and OpenAI-compatible embedding calls now consistently apply the configured SSL client setting, improving reliability for deployments that require custom certificate or verification behavior. [Commit](https://github.com/open-webui/open-webui/commit/fd25152076ea7c310e42c9bacc5cd2b544eeae48), [Commit](https://github.com/open-webui/open-webui/commit/56c5bc1d3487020ab886d3332aacc1644c1d6123) +- 🧭 **Scheduled Tasks calendar reliability.** Scheduled Tasks is now handled as a virtual automation calendar that appears only when automation access is available, and calendar selection now filters by stable ID instead of name so event forms behave consistently. [Commit](https://github.com/open-webui/open-webui/commit/1d501cfa3f96b3a9a5f4f7ce996947671fd09f29), [Commit](https://github.com/open-webui/open-webui/commit/24dd5b461eb44d306c823389e0f664c45db042e8) +- 🛡️ **Protected calendar deletion rules.** System and default calendars can no longer be deleted, preventing accidental removal of built-in calendar functionality. [Commit](https://github.com/open-webui/open-webui/commit/51627555bf356c8ec663f4d2f43f2f013eadbce4) +- 🖼️ **Image SSL setting support.** Image generation now respects the configured SSL session setting, preventing avoidable connection failures in strict certificate environments. [Commit](https://github.com/open-webui/open-webui/commit/128cf41fcedf2638fc8a6acd850d8b0409be1c4e), [#23777](https://github.com/open-webui/open-webui/issues/23777) +- 🗂️ **Folder ownership assignment hardening.** Folder create and update inputs now reject unexpected extra fields, preventing clients from overriding protected values like ownership through mass-assignment payloads. [#23648](https://github.com/open-webui/open-webui/pull/23648) +- 🔐 **Knowledge file deletion ownership checks.** Collaborators with knowledge base write access can no longer permanently delete files they do not own, preventing unintended file removal across other linked chats and knowledge bases. [Commit](https://github.com/open-webui/open-webui/commit/914ccf07ef158afe5588b97ed42778c93c439938), [#23636](https://github.com/open-webui/open-webui/pull/23636#issuecomment-4232439454) +- 🗑️ **Knowledge deletion reliability.** Deleting a knowledge base by ID now completes reliably without unexpected failures. [Commit](https://github.com/open-webui/open-webui/commit/7e453de4f7794ff386e285aa5951b94e926ec273), [#23776](https://github.com/open-webui/open-webui/issues/23776), [#23814](https://github.com/open-webui/open-webui/pull/23814) +- 🔐 **OAuth 2.1 PKCE enforcement.** OAuth 2.1 providers now default to S256 PKCE even when discovery metadata omits supported challenge methods, preventing login failures with providers that require PKCE by default. [#23667](https://github.com/open-webui/open-webui/issues/23667), [Commit](https://github.com/open-webui/open-webui/commit/050c4b97a95addc5eaeef86ba00631673a90dec4) +- 🔐 **Static OAuth scope handling.** Static OAuth credential flows now prioritize administrator-defined scopes and handle OAuth 2.1 static flow behavior more reliably. [Commit](https://github.com/open-webui/open-webui/commit/349ea4ea9e577f2cbfb4917ef5f52e5ac53c5b70), [#23668](https://github.com/open-webui/open-webui/issues/23668), [#23696](https://github.com/open-webui/open-webui/pull/23696), [#23783](https://github.com/open-webui/open-webui/pull/23783) +- 🔐 **Static OAuth tool registration reliability.** Static OAuth tool server registration now resolves and uses saved admin credentials more reliably, preventing registration failures when valid client credentials are provided. [#23670](https://github.com/open-webui/open-webui/issues/23670), [Commit](https://github.com/open-webui/open-webui/commit/2943955c529138c0e530fd07b6333a0052e3684e), [Commit](https://github.com/open-webui/open-webui/commit/c767bcaa739f76b1a4337dfd9d6be47adb504825) +- ⏳ **OAuth token expiry fallback.** OAuth sessions now always store a safe expiry value even when providers omit "expires_in" or "expires_at", so token refresh checks continue working and tool calls are less likely to fail later with unexpected authorization errors. [#23669](https://github.com/open-webui/open-webui/issues/23669), [Commit](https://github.com/open-webui/open-webui/commit/31406caa795173a59d5843d3601b891bf617cbaa) +- 🔑 **Anthropic x-api-key model access.** Anthropic-compatible clients can now authenticate with the "x-api-key" header across all relevant API routes, so model listing requests like GET "/api/v1/models" no longer fail with unauthorized errors. [#23319](https://github.com/open-webui/open-webui/issues/23319), [Commit](https://github.com/open-webui/open-webui/commit/611fe0c8a938539b73b559e84964f40c30bf436d) +- 🔑 **SSO password option visibility.** Account settings now hide password change controls when password-change access is disabled, avoiding misleading password options for SSO-focused setups. [#15292](https://github.com/open-webui/open-webui/issues/15292), [Commit](https://github.com/open-webui/open-webui/commit/cced77b584d6ea46c58fecddb2b3dd5e955c8417) +- 🔑 **Open Terminal MCP authentication.** Open Terminal MCP tool calls now include the configured API key when calling internal routes, preventing unauthorized errors for commands like file reads and command execution. [#106](https://github.com/open-webui/open-terminal/pull/106) +- 🧯 **Provider error freeze recovery.** Task-based chat requests now surface provider HTTP errors through normal failure handling, so content-filter and other upstream 4xx responses no longer leave chats stuck in a perpetual loading state. [#23663](https://github.com/open-webui/open-webui/issues/23663), [Commit](https://github.com/open-webui/open-webui/commit/96265cf042c8ab97dbec5d0efcce8010d0cd76e5) +- 🔄 **Immediate outlet filter updates.** Assistant messages modified by outlet filters now appear correctly as soon as streaming completes, without requiring a page refresh. [#23829](https://github.com/open-webui/open-webui/pull/23829) +- 🌊 **Middleware cancellation reliability.** Long-running requests now complete more reliably by preventing middleware-level cancellations from interrupting in-flight database and embedding work, reducing unexpected failures and noisy error logs when connections close early. [#23709](https://github.com/open-webui/open-webui/pull/23709) +- 🚦 **Async vector search responsiveness.** File processing, memory updates, and knowledge retrieval no longer block the server event loop during vector database operations, so other chats and requests stay responsive while indexing or search is running. [#23706](https://github.com/open-webui/open-webui/pull/23706) +- 🗒️ **Notes chat llama.cpp compatibility.** Notes AI chat no longer sends empty assistant prefill messages that can conflict with reasoning-enabled llama.cpp responses, preventing immediate 400 errors in Notes conversations. [Commit](https://github.com/open-webui/open-webui/commit/fd93bd3414a1725219e14561bc5640b62f9fd4a1), [#23703](https://github.com/open-webui/open-webui/issues/23703#issuecomment-4243907629) +- 🧩 **Ollama thinking field preservation.** Messages modified by filters now keep the Ollama "thinking" field when sent to the model, so reasoning-aware workflows and custom filter-based passthrough setups work reliably. [Commit](https://github.com/open-webui/open-webui/commit/8bd23b91459914eb7df5b5a66567d3544e0da168), [#22508](https://github.com/open-webui/open-webui/issues/22508) +- 🧾 **Reasoning content preservation.** Assistant tool-call messages now retain reasoning content across turns, improving reliability for reasoning-heavy model workflows. [Commit](https://github.com/open-webui/open-webui/commit/3dd8255816898467246c81cba3c9bc48bc18d86d), [#23175](https://github.com/open-webui/open-webui/issues/23175), [#23742](https://github.com/open-webui/open-webui/pull/23742) +- 🧭 **Background task scoping for new chats.** Chat title and auto-tag generation now run only for the first message of a new conversation and only once in multi-model responses, preventing duplicate or incorrectly triggered background tasks in follow-up flows. [Commit](https://github.com/open-webui/open-webui/commit/f102060a6d85db4acd3d0bf5c25e976f36cd5533..a4ed16999eec9a654a37c2bb4c15ba5ecd1fa3b7) +- 📚 **Channel document context retention.** Channel conversations now preserve and load the correct stored message history so model responses can use uploaded and retrieved document context more reliably. [#23686](https://github.com/open-webui/open-webui/issues/23686), [Commit](https://github.com/open-webui/open-webui/commit/cf4218e688def6f11d195aeda6665ae5b5376b67), [Commit](https://github.com/open-webui/open-webui/commit/18fe17127a7175579506e7456d3e5aba201371e6) +- ⏳ **Interrupted response recovery.** Assistant placeholder messages now start as incomplete and recover more safely after interrupted generations, preventing silent empty replies after refreshes or dropped requests. [#23176](https://github.com/open-webui/open-webui/issues/23176), [Commit](https://github.com/open-webui/open-webui/commit/c8ef7b028931263e8773cb60a7111d80d9572d26), [Commit](https://github.com/open-webui/open-webui/commit/cf4218e688def6f11d195aeda6665ae5b5376b67) +- 🧰 **Large tool result rendering.** Tool call details now display large result payloads reliably in chat instead of intermittently showing empty output for bigger tool responses. [#18743](https://github.com/open-webui/open-webui/issues/18743), [Commit](https://github.com/open-webui/open-webui/commit/45e49d33e51f7720c00b564215484aff9b48b20c) +- 🧼 **Null-byte document sanitization.** PDF and other document ingests now sanitize null bytes and invalid surrogate characters before pgvector writes, preventing PostgreSQL upload failures and allowing affected files to index successfully. [#22992](https://github.com/open-webui/open-webui/issues/22992), [Commit](https://github.com/open-webui/open-webui/commit/8dba798cce9fb1efc5f6acc5f37b152662db78d7) +- 📝 **Knowledge text editor stability.** The Knowledge "Add Text Content" modal now uses a plain text editor, avoiding current rich text editor issues and keeping drafting behavior consistent with existing knowledge editing flows. [Commit](https://github.com/open-webui/open-webui/commit/cd55c3e21237e000c13c6f396bb95b261f3bda82) +- 🎤 **STT SSL setting consistency.** Speech and related outbound media requests now consistently use shared async HTTP sessions and honor the configured SSL verification setting, improving compatibility with self-signed deployments. [#23672](https://github.com/open-webui/open-webui/issues/23672), [Commit](https://github.com/open-webui/open-webui/commit/2ddcb30b9a519885422ba1f36cc3485a7d897bf8) +- 🎙️ **Mistral speech input format.** Mistral speech-to-text requests now use the correct chat-completions audio input format for better compatibility. [Commit](https://github.com/open-webui/open-webui/commit/34d569d564a8ef2702c647dbad83eac840b76b2e), [#23822](https://github.com/open-webui/open-webui/issues/23822) +- 🖼️ **Optional image size parameter.** Image generation no longer sends the "size" field when no size is configured, improving compatibility with providers that reject unsupported size arguments. [#23611](https://github.com/open-webui/open-webui/issues/23611), [Commit](https://github.com/open-webui/open-webui/commit/869cf9e848b741705dc058550fa1b3f70db47fe8) +- 🔎 **FireCrawl timeout reliability.** FireCrawl web loading now uses direct scrape requests and improved timeout handling for single-URL fetches, reducing empty results and premature timeout failures with local FireCrawl setups. [#23411](https://github.com/open-webui/open-webui/issues/23411), [Commit](https://github.com/open-webui/open-webui/commit/9c64d84ad90804bf7d891e4a5097c03c4d7044c3) +- 🖱️ **Custom action icon drag prevention.** Custom user-added action icons in chat responses are no longer accidentally draggable, so clicks and hover interactions behave consistently with built-in action icons. [#23412](https://github.com/open-webui/open-webui/pull/23412) +- 🖼️ **Image URL conversion reliability.** Sending image URLs to AI models no longer fails with "cannot pickle 'coroutine' object" errors, so image inputs now convert to base64 reliably during request processing. [#23685](https://github.com/open-webui/open-webui/pull/23685#issuecomment-4240424635) +- 📂 **Channel input menu dismissal.** In Workspace Channels, the message input dropdown now closes immediately after selecting "Upload Files" or "Capture", matching normal chat input behavior and preventing the menu from staying open unnecessarily. [#23684](https://github.com/open-webui/open-webui/pull/23684) +- 📋 **Clipboard copy scroll stability.** Copying content with the fallback clipboard method no longer triggers unwanted page scrolling during focus, keeping your current reading position stable. [Commit](https://github.com/open-webui/open-webui/commit/fc98000aa8d439bbff21a70370f5e962bf23f4bc) +- 🖼️ **Profile image URL validation.** Profile saves now accept valid Open WebUI profile-image paths, trusted external HTTP(S) avatar URLs, and safe raster data-image formats while rejecting unsafe URL patterns that could be abused. [#23389](https://github.com/open-webui/open-webui/pull/23389) +- 👤 **Partial user profile updates.** User update API requests can now modify only the fields you provide, so administrators no longer need to resubmit unchanged name, email, and profile image values when changing a single setting like role. [#23424](https://github.com/open-webui/open-webui/issues/23424), [Commit](https://github.com/open-webui/open-webui/commit/3c2c611ba91d794a1e73134ec41b0de2b3927677) +- 🚨 **Provider SSE error visibility.** Provider failures returned with streaming content types are now surfaced as proper API errors and logged clearly, so issues like context-window limits no longer fail silently during chat generation. [#23379](https://github.com/open-webui/open-webui/pull/23379) +- 🧵 **Queued prompt race prevention.** Chat request queues now prevent overlapping processing for the same chat, avoiding duplicate queue handling when multiple queue-processing triggers fire close together. [#23181](https://github.com/open-webui/open-webui/issues/23181), [Commit](https://github.com/open-webui/open-webui/commit/e10a00132eed54a0108fb6ac120e8229deef3656) +- 🛑 **Cancellation event delivery reliability.** Cancelled chat processing now safely emits task-cancel and error events only when an event emitter is available, while provider HTTP errors now also route through task-cancel handling so chats recover from blocked-loading states more reliably. [#23663](https://github.com/open-webui/open-webui/issues/23663), [Commit](https://github.com/open-webui/open-webui/commit/51765b619c8584b042af68c3a5c87525a105ccd8), [Commit](https://github.com/open-webui/open-webui/commit/96265cf042c8ab97dbec5d0efcce8010d0cd76e5) +- 🔑 **OIDC key-rotation recovery.** OIDC login now retries token authorization with refreshed provider signing keys after a bad-signature failure, so logins recover automatically after identity-provider key rotation without requiring a service restart. [#23582](https://github.com/open-webui/open-webui/issues/23582), [Commit](https://github.com/open-webui/open-webui/commit/facb194a07486e847f0725a0a839e99b5864d37b) +- 🌍 **Non-ASCII tag filtering.** Prompt and model tag filters now handle non-Latin tags more reliably across SQLite and PostgreSQL, so tags like Cyrillic values return the expected items in Workspace lists. [#23381](https://github.com/open-webui/open-webui/issues/23381), [#23427](https://github.com/open-webui/open-webui/pull/23427), [Commit](https://github.com/open-webui/open-webui/commit/57784706e4fee75dec67e20b0d89a97351ac6256) +- 🏷️ **Prompt tag query accuracy.** Prompt tag filtering now uses JSON-element-aware queries so tag-based lookups return the correct prompts. [Commit](https://github.com/open-webui/open-webui/commit/e7e752f8e74e7b01fe2e6cb56f06e99312e1afe7), [#23386](https://github.com/open-webui/open-webui/pull/23386) +- 🗃️ **SQLite async pool compatibility.** SQLite async database setup no longer forces an explicit queue pool class, avoiding pool configuration conflicts in SQLite deployments. [Commit](https://github.com/open-webui/open-webui/commit/26b8ca5b5eeb144fae3fe6eaeae826150d8af826) +- 🧠 **Knowledge embedding deadlock prevention.** Knowledge file processing now runs blocking vector-save work in a worker thread while keeping async status updates reliable, preventing file processing from stalling during long embedding operations. [Commit](https://github.com/open-webui/open-webui/commit/d4b90f93bda2413ec8f040e61959acdb7b242061), [Commit](https://github.com/open-webui/open-webui/commit/22cfb3c673cbfa4a6bce26fde8e2e2754ce4963b) +- 🤖 **Automation worker async DB handling.** Automation claiming and run recording now use async database sessions consistently, improving worker stability for scheduled automations. [Commit](https://github.com/open-webui/open-webui/commit/cb6e77be3ec6ce00dd1f5b9ce3a655e6f65bc5da) +- 🕒 **Automation timezone scheduling.** Scheduled automations now calculate each user’s next run time using that user’s saved timezone, preventing run drift caused by server-time fallback. [Commit](https://github.com/open-webui/open-webui/commit/a4d62253df55c6307112eb76a6bfa29a7f538e21) +- 🔎 **Notes search matching.** Notes search now handles multi-word and hyphenated queries more reliably, so relevant notes and snippets are easier to find from partial phrase searches. [Commit](https://github.com/open-webui/open-webui/commit/a35926261646f8897ba71da1572ed5dff802e3be) +- 📐 **Display math rendering.** Chat markdown now correctly recognizes and renders "$$...$$" expressions as display math, improving reliability for multiline and escaped KaTeX content while keeping malformed delimiters from disrupting message rendering. [#23526](https://github.com/open-webui/open-webui/issues/23526), [Commit](https://github.com/open-webui/open-webui/commit/15b89b9218b7d2c7239c579aa3d23c2892227ac6) +- 🚫 **LDAP empty-password rejection.** LDAP login now rejects empty or whitespace-only passwords before bind attempts, preventing unauthenticated simple-bind behavior from granting access on permissive LDAP server configurations. [#23633](https://github.com/open-webui/open-webui/pull/23633) +- 🌐 **IPv6 SSRF address blocking.** URL validation now uses standard IP address checks for both IPv4 and IPv6, preventing private, loopback, link-local, reserved, and mapped-address SSRF bypasses through IPv6 hostname resolution. [#23453](https://github.com/open-webui/open-webui/pull/23453) +- 🔒 **API key endpoint restriction bypass.** API key endpoint restrictions are now enforced regardless of whether the key is sent through Authorization headers, cookies, or "x-api-key", preventing bypass through alternate key transport paths. [#23637](https://github.com/open-webui/open-webui/pull/23637) +- 🔐 **Channel sharing permission enforcement.** Channel creation and updates now enforce allowed access grant rules for public sharing, preventing unauthorized wildcard sharing on group channels. [#23638](https://github.com/open-webui/open-webui/pull/23638) +- 🛑 **Socket role invalidation.** Socket sessions now disconnect automatically when a user is demoted or deleted, preventing stale admin privileges from persisting until reconnect. [#23642](https://github.com/open-webui/open-webui/pull/23642) +- 🛂 **Tool server access checks.** Tool listing now correctly awaits server access checks, preventing users from seeing server-backed tools they do not have permission to use. [Commit](https://github.com/open-webui/open-webui/commit/d40f31982be3eed37e55e3f67b1eea9a5dc8c525) +- 🛑 **Task endpoint access control.** Global task listing and direct task stop endpoints are now restricted to administrators, while regular users can stop only their own chat tasks through a scoped chat endpoint. [#23454](https://github.com/open-webui/open-webui/pull/23454) +- 🧱 **Redis cache key isolation.** Tool server and terminal server cache entries now include the Redis key prefix, preventing multiple Open WebUI instances that share one Redis database from overwriting each other’s cached connection data. [#23649](https://github.com/open-webui/open-webui/pull/23649) +- 🧠 **Client session leak prevention.** Outbound provider requests now use a shared session pool with safer response cleanup and shutdown handling, preventing aiohttp session buildup and reducing memory growth during heavy concurrent API traffic. [#23540](https://github.com/open-webui/open-webui/issues/23540), [Commit](https://github.com/open-webui/open-webui/commit/c47dd7b7717c4186e0f0549ca3c8cb4d9bb38135) +- 🧩 **Tool enum value handling.** Tool schema generation now safely handles enum values as strings, preventing failures when OpenAPI parameters include non-string enum entries. [#23597](https://github.com/open-webui/open-webui/issues/23597), [Commit](https://github.com/open-webui/open-webui/commit/4498e6faf2b1bdd1caa0e2c1c15d90a2790cd721) +- 🧷 **Responses model access control.** The OpenAI-compatible Responses endpoint now enforces per-model permissions, preventing non-admin users from accessing models they are not allowed to use. [#23481](https://github.com/open-webui/open-webui/pull/23481) +- 🛡️ **Collection process endpoint permissions.** Collection processing endpoints now enforce collection ownership checks for web and text processing requests. [Commit](https://github.com/open-webui/open-webui/commit/ba83613ff297bc82db660b5273f04672d744902f), [#23634](https://github.com/open-webui/open-webui/pull/23634) +- 📚 **Knowledge query access enforcement.** Knowledge-base collection queries now block unauthorized enumeration and require read access before returning results. [Commit](https://github.com/open-webui/open-webui/commit/860b90fd17d14ba00674621edd294dee150491d2), [#23635](https://github.com/open-webui/open-webui/pull/23635), [#23452](https://github.com/open-webui/open-webui/pull/23452) +- 🔍 **RAG collection query permissions.** Vector search collection queries now enforce access checks before retrieval results are returned. [Commit](https://github.com/open-webui/open-webui/commit/f44b7a01f5b854f47c1594a1ab5f72096f736262), [#23627](https://github.com/open-webui/open-webui/pull/23627) +- 🔗 **Chained base model access checks.** Chained base model execution now enforces per-model access rules to prevent unauthorized model usage. [Commit](https://github.com/open-webui/open-webui/commit/8acce144f99992b75c25f0e5038b16881ce9f066), [Commit](https://github.com/open-webui/open-webui/commit/50363ba66b19613a2fc0cab6a3f7f724a825135e), [#23647](https://github.com/open-webui/open-webui/pull/23647) +- ✍️ **Collaborative document write checks.** Collaborative document updates now require proper write permission before changes are accepted. [Commit](https://github.com/open-webui/open-webui/commit/638c7ab80216452910bdc59a19eb90e6b7244c6c), [Commit](https://github.com/open-webui/open-webui/commit/3271b013a8b30a882364679dcb40ffc9a89f037e), [#23624](https://github.com/open-webui/open-webui/pull/23624) +- 📥 **Model import ownership validation.** Model import now enforces ownership and access grant checks to prevent unauthorized imports. [Commit](https://github.com/open-webui/open-webui/commit/499129625bf96b2c03a6d057a2f91fdf07fd1c49), [#23628](https://github.com/open-webui/open-webui/pull/23628) +- 🚫 **Inactive member channel access.** Deactivated group members can no longer read or write channel content through direct API calls, so channel permissions now match active membership status. [#23623](https://github.com/open-webui/open-webui/pull/23623) +- 🎛️ **Ollama endpoint model permissions.** Restricted models are now protected on Ollama show, generate, embed, and embeddings endpoints, preventing authenticated users from using private models without read access. [#23631](https://github.com/open-webui/open-webui/pull/23631) +- 🧭 **Azure deployment path validation.** Azure model names are now validated and safely encoded before request URL construction, preventing path traversal attempts from reaching unintended Azure endpoints. [#23629](https://github.com/open-webui/open-webui/pull/23629) +- 👥 **Private channel member list access.** Standard channel member lists now require proper read permission, preventing unauthorized users from enumerating members of private channels by direct API calls. [#23625](https://github.com/open-webui/open-webui/pull/23625) +- 🌀 **Tool server schema recursion safety.** Tool server OpenAPI conversion now handles circular request schema references safely, preventing conversion crashes and ensuring one bad tool server spec does not break the full tool server list. [#23588](https://github.com/open-webui/open-webui/pull/23588), [Commit](https://github.com/open-webui/open-webui/commit/d3df8f1f372411314be9121fbf61d107939fa258) +- 🧱 **Safer file path handling.** File upload, transcription cache, and model download paths now use safer path construction helpers to reduce path parsing risks and improve cross-platform path safety. [Commit](https://github.com/open-webui/open-webui/commit/15f9a8f3f13f112c96cb1b16f88859f65de58346) +- 🧾 **Prompt save error feedback.** Saving prompt edits now shows a clear error toast if the save fails, so failed updates are visible instead of silently failing in the editor flow. [Commit](https://github.com/open-webui/open-webui/commit/36a81ad43b7c0d450079f818a7546eaa517e3d95) +- 🧾 **Tool call JSON rendering.** Tool call arguments and structured results now render as plain formatted JSON blocks instead of markdown code fences, preventing formatting quirks and making tool output easier to read consistently. [Commit](https://github.com/open-webui/open-webui/commit/a7d4c53f3adb80768b67e4a410b486b04a581521) +- 👥 **First-user admin race protection.** Concurrent first-time LDAP or OAuth registrations can no longer create multiple admin accounts, so only the true first account is promoted during initial setup. [#23626](https://github.com/open-webui/open-webui/pull/23626) +- 🔒 **SCIM token checks.** SCIM authentication now compares tokens in a safer way, helping prevent timing-based token guessing attacks. [#23577](https://github.com/open-webui/open-webui/pull/23577) +- 🔒 **Safer file access checks.** HTML file previews now treat missing or non-admin owners as inaccessible, preventing accidental access to files that should not be shown. [Commit](https://github.com/open-webui/open-webui/commit/6acaaea59a50ec26da03e6144017a2fd86241ce9) +- 🖼️ **ComfyUI request hangs.** Concurrent image generation and editing requests to ComfyUI now complete reliably instead of getting stuck when the same user starts multiple requests at once. [#23592](https://github.com/open-webui/open-webui/pull/23592), [#23591](https://github.com/open-webui/open-webui/issues/23591) +- 🧭 **Permission-aware built-in tools.** Built-in tools now consistently respect user feature permissions for memories, web search, image generation, code interpreter, notes, channels, and automations, preventing tools from being exposed to users without access. [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) +- 🛑 **Interrupted MCP cleanup stability.** Interrupted MCP tool calls no longer leave runaway cleanup behavior that can drive container CPU usage to 100%, keeping instances stable after cancellations or dropped connections. [#23143](https://github.com/open-webui/open-webui/issues/23143) +- 🚪 **OAuth redirect URI reliability.** OAuth login redirects now use provider client metadata more consistently, preventing incorrect HTTP callback URLs behind reverse proxies and improving sign-in reliability for providers such as Feishu. [#23203](https://github.com/open-webui/open-webui/pull/23203), [#23128](https://github.com/open-webui/open-webui/issues/23128) +- 🌐 **OAuth redirect handling.** OAuth provider token exchange now follows redirects automatically, improving sign-in reliability with identity providers that redirect token endpoint requests. [#23409](https://github.com/open-webui/open-webui/issues/23409), [Commit](https://github.com/open-webui/open-webui/commit/498ff8cdc3dd47000cdc60e5adcf36f4adfbe07d) +- ☁️ **OneDrive picker redirect handling.** OneDrive file picker authentication now uses the current app origin as the redirect URI, improving sign-in reliability when launching the picker from deployed environments. [#23450](https://github.com/open-webui/open-webui/issues/23450), [Commit](https://github.com/open-webui/open-webui/commit/21cc8281323d505d7d084cc496bd433063315c86) +- 🍪 **OAuth session cookie persistence.** OIDC sign-in now correctly sets the "oauth_session_id" cookie, so "system_oauth" connections can forward user OAuth tokens to upstream providers as expected. [#23251](https://github.com/open-webui/open-webui/pull/23251), [#23250](https://github.com/open-webui/open-webui/issues/23250) +- 🔑 **OAuth session cookie handling.** OAuth callback processing no longer fails on undefined cookie expiry data, so OAuth session cookies are stored correctly after sign-in. [#23207](https://github.com/open-webui/open-webui/pull/23207), [#23197](https://github.com/open-webui/open-webui/issues/23197) +- 🔏 **Ollama SSL handling.** Ollama model management and file uploads now respect the configured SSL verification setting, so self-signed certificates work when SSL verification is disabled. [#23503](https://github.com/open-webui/open-webui/issues/23503), [Commit](https://github.com/open-webui/open-webui/commit/e51b661af0e71a24f041428f328fcc6e97a15262) +- 🛡️ **OAuth avatar URL validation.** OAuth sign-in now validates profile picture URLs before fetching them, preventing invalid image links from causing login-time errors. [#23356](https://github.com/open-webui/open-webui/pull/23356) +- 🔑 **User invite token expiry.** New user invite logins now respect the configured "JWT_EXPIRES_IN" setting, so signup tokens expire as expected instead of using the default lifetime. [#23576](https://github.com/open-webui/open-webui/pull/23576) +- 🚪 **Channel access checks.** Channel actions now verify the current user when checking access, improving permission enforcement across channel views and message actions. [Commit](https://github.com/open-webui/open-webui/commit/4632f200a9ac98c915aee412b34e86c3d3c58bb1) +- 📣 **Channel message lookups.** Channel message details and pinning now work more reliably when the sender account is missing, avoiding failures in those views. [Commit](https://github.com/open-webui/open-webui/commit/6acaaea59a50ec26da03e6144017a2fd86241ce9) +- 📌 **Pinned webhook message handling.** Viewing pinned webhook messages now works reliably even when webhook profile data is missing, preventing server errors and frontend crashes in channel pinned message dialogs. [#23414](https://github.com/open-webui/open-webui/pull/23414) +- 🛡️ **Note edit permission enforcement.** Note saving now requires write access instead of read access, preventing unauthorized users from modifying notes while preserving expected collaboration permissions. [Commit](https://github.com/open-webui/open-webui/commit/584a9a0920d8c8c72fc89ccbac83c970b5a4bd4a) +- 🗂️ **Archived chats menu visibility.** The 'Archived Chats' option in the user menu is now shown reliably for all users, so non-admin accounts can consistently access archived conversations. [Commit](https://github.com/open-webui/open-webui/commit/07262fa62c2323fc7948389e5b5b8a5d1b72fade) +- 💾 **Error message persistence.** LLM errors that occur during streaming are now saved to the database even if the connection drops, so users can see what went wrong when they reconnect. [#23231](https://github.com/open-webui/open-webui/pull/23231) +- 🚫 **Missing message completion guard.** Chat completion finalization now skips invalid requests without a message identifier, preventing unnecessary error toasts caused by rare frontend concurrency timing. [#23184](https://github.com/open-webui/open-webui/pull/23184) +- 🧠 **Active message completion accuracy.** Switching chats or refreshing during generation no longer marks the currently streaming assistant message as finished too early, so thinking blocks and action buttons appear at the correct time. [#23171](https://github.com/open-webui/open-webui/issues/23171) +- 📞 **Call overlay visibility.** Incoming call events now open the call overlay and controls reliably, preventing cases where the call interface briefly appeared and then disappeared. [Commit](https://github.com/open-webui/open-webui/commit/ee9db91df02120e1e3651e8881734966b710ad52) +- 💬 **Prompt submission handling.** Chat messages now preserve attached files more reliably when prompts are sent, including queued messages and shared prompt actions. [Commit](https://github.com/open-webui/open-webui/commit/6d6dfbf02c893d72d85d4490cb41f1665b1f9f95) +- 🧾 **Prompt variable form saving.** Prompt variable forms now save reliably without runtime errors or an unresponsive save action, so input values and placeholders work correctly when applying prompt templates with variables. [#23225](https://github.com/open-webui/open-webui/issues/23225), [#23480](https://github.com/open-webui/open-webui/issues/23480) +- 🛟 **Task model fallback safety.** Task routing now handles missing default model entries safely, preventing task execution failures when the previously selected model is no longer available. [#23169](https://github.com/open-webui/open-webui/pull/23169) +- 📊 **Usage statistic preservation.** Follow-up generation no longer overwrites existing token usage fields, so stored usage statistics remain accurate for the main response. [#23152](https://github.com/open-webui/open-webui/issues/23152) +- 📝 **Writing block parsing reliability.** ":::writing" blocks now parse more reliably when headers or extra inline text are present, preventing malformed rendering and duplicate output artifacts. [#23174](https://github.com/open-webui/open-webui/issues/23174) +- 🧾 **Code block line break reliability.** Blank lines in submitted code blocks are now preserved more reliably instead of being collapsed. [Commit](https://github.com/open-webui/open-webui/commit/1be9627dd27ffe75957729a4a0d1682a98684f01), [#20302](https://github.com/open-webui/open-webui/issues/20302), [#23451](https://github.com/open-webui/open-webui/pull/23451) +- ✂️ **Citation spacing cleanup.** When citations are disabled for a model, citation markers and their leftover spacing are now removed together so punctuation and copied text remain cleanly formatted. [#23141](https://github.com/open-webui/open-webui/issues/23141) +- 🧰 **Pipe tool access.** Pipe functions now receive built-in and MCP tools in __tools__, so tools like Web Search and code execution are available when enabled. [#23365](https://github.com/open-webui/open-webui/issues/23365) +- 📚 **Batch file processing database handling.** Batch knowledge file processing now consistently uses the active database session, preventing failures caused by missing database context during file ownership checks and update writes. [#23137](https://github.com/open-webui/open-webui/issues/23137) +- ⚙️ **Default model parameter loading.** The "DEFAULT_MODEL_PARAMS" environment variable is now parsed and applied correctly, so default generation settings are honored reliably without being ignored at startup. [#23223](https://github.com/open-webui/open-webui/pull/23223) +- 🔧 **Web search settings save reliability.** Saving web search configuration now works without server errors, so administrators can update "WEB_FETCH_MAX_CONTENT_LENGTH" and related retrieval settings successfully from the admin interface. [Commit](https://github.com/open-webui/open-webui/commit/36d02aa1477aa1b4e7fb59d022f99693ebfa8667), [#23127](https://github.com/open-webui/open-webui/issues/23127) +- 🔍 **Web search result count.** The built-in search_web tool now respects the admin-configured "Search Result Count" setting instead of always returning 5 results when using Native Function Calling mode. [#23488](https://github.com/open-webui/open-webui/pull/23488), [#23485](https://github.com/open-webui/open-webui/issues/23485) +- 🖼️ **Open Terminal file response handling.** Open Terminal tool responses now preserve binary content types in user-side connections, so image and non-text file reads work consistently instead of being forced into plain text. [#23125](https://github.com/open-webui/open-webui/issues/23125) +- 🖥️ **Terminal label casing.** Terminal names in the chat input now display exactly as stored instead of being automatically capitalized, so domain-style server names appear correctly. [#23518](https://github.com/open-webui/open-webui/pull/23518) +- 🖼️ **Gravatar profile photo saving.** Gravatar profile images can now be saved successfully from account settings, with clearer validation and error handling instead of failing with generic object errors. [#23156](https://github.com/open-webui/open-webui/issues/23156) +- 🪟 **Details expansion preference.** Tool call detail groups now honor the 'Always Expand Details' chat setting, so they open expanded by default when that preference is enabled. [#23262](https://github.com/open-webui/open-webui/pull/23262), [#23255](https://github.com/open-webui/open-webui/issues/23255) +- 🖱️ **Rapid sidebar action protection.** Archive and delete actions in the chat sidebar now ignore repeated clicks while a request is in progress, preventing duplicate requests and stacked error toasts. [#23172](https://github.com/open-webui/open-webui/issues/23172) +- 📲 **Mobile model selector positioning.** The mobile model selector dropdown now applies a constrained viewport width and left offset, preventing overflow and making model selection easier on small screens. [#23310](https://github.com/open-webui/open-webui/pull/23310) +- 🔽 **Task list toggle icons.** The task list collapse button now shows the correct arrow direction, making task sections easier to expand and collapse at a glance. [Commit](https://github.com/open-webui/open-webui/commit/f66b67c8b86b6f9d896a23c7bb53907c2e6b15d3), [#23354](https://github.com/open-webui/open-webui/issues/23354) +- ➕ **Attachment menu auto-close.** The chat attachment menu now closes immediately after selecting upload actions like file upload, camera capture, web attach, Google Drive, or OneDrive, preventing the menu from lingering on screen. [Commit](https://github.com/open-webui/open-webui/commit/4764dd5d3765c22384ed38cbc97a8170daa7a75f), [#23320](https://github.com/open-webui/open-webui/issues/23320) +- 🧹 **Per-chat draft clearing.** Sent message drafts are now cleared using the active chat key, so sent text no longer reappears in the input after a refresh. [Commit](https://github.com/open-webui/open-webui/commit/124b7e9154d7f3ca8a16f2b90621209ac8d6b8c1), [#23296](https://github.com/open-webui/open-webui/issues/23296) +- ✉️ **Context-aware input action button.** The input now shows the send action when text or files are present during generation, while keeping stop controls for truly empty input states to avoid action confusion. [Commit](https://github.com/open-webui/open-webui/commit/86472bb4453af7ea4e5ddc8d127b14d8e67733bc), [#23306](https://github.com/open-webui/open-webui/issues/23306) +- 📉 **Pyodide prompt cache stability.** Pyodide code interpreter context is now appended to the system prompt instead of user messages, preserving stable prefix caching across turns and reducing repeated token costs in long native tool-calling chats. [#23269](https://github.com/open-webui/open-webui/issues/23269) +- 🧪 **Temp chat outlet filtering.** Outlet filters now process temporary chats more reliably, preserving assistant output and usage data so local chat responses stay consistent when filter pipelines are enabled. [Commit](https://github.com/open-webui/open-webui/commit/70a6a24f143b221c787bc50b72582ee1e0c2dac0) + +### Changed + +- ⚠️ **Database Migrations**: This release includes database schema changes; we strongly recommend backing up your database and all associated data before upgrading in production environments. If you are running a multi-worker, multi-server, or load-balanced deployment, all instances must be updated simultaneously, rolling updates are not supported and will cause application failures due to schema incompatibility. +- 🧨 **Plugin async migration required.** Custom plugins for Tools, Functions, and Pipelines may require migration to the new async backend signatures after upgrading, so plugin maintainers should update handlers and database call patterns for compatibility and follow the 0.9.0 plugin migration guide. [Migration Guide](https://docs.openwebui.com/features/extensibility/plugin/migration/to-0.9.0) +- 🔄 **Automation terminal source.** Automations now use the terminal configured on the selected model instead of a separate per-automation terminal picker, keeping terminal behavior consistent between chat and scheduled runs. [Commit](https://github.com/open-webui/open-webui/commit/47d413ce7b2a006a8126f4a9055b13e5fcb33a1d) +- 🚧 **OpenAI passthrough now opt-in.** Direct OpenAI catch-all proxy requests are now disabled by default and require enabling "ENABLE_OPENAI_API_PASSTHROUGH", so deployments relying on passthrough must explicitly turn it on after upgrading. [#23640](https://github.com/open-webui/open-webui/pull/23640) +- 🗄️ **SQLite WAL default enabled.** SQLite deployments now default to enabling write-ahead logging, improving concurrent read and write behavior without requiring manual configuration. [Commit](https://github.com/open-webui/open-webui/commit/2f9e326dba3b1087932cb6b8075ed1881bd1c6d6) + ## [0.8.12] - 2026-03-26 ### Added From 5f76c250f880d07ebc9b856b604cc1b8029f7eb4 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 15:20:28 +0900 Subject: [PATCH 107/119] refac --- src/lib/components/chat/MessageInput/TerminalMenu.svelte | 2 +- src/lib/components/chat/SettingsModal.svelte | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/MessageInput/TerminalMenu.svelte b/src/lib/components/chat/MessageInput/TerminalMenu.svelte index 8aaf880c90..ca721ceb2d 100644 --- a/src/lib/components/chat/MessageInput/TerminalMenu.svelte +++ b/src/lib/components/chat/MessageInput/TerminalMenu.svelte @@ -125,7 +125,7 @@ class="p-0.5 rounded-md text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition" on:click|stopPropagation={() => { show = false; - showSettings.set(true); + showSettings.set('tools'); }} > Date: Tue, 21 Apr 2026 15:41:07 +0900 Subject: [PATCH 108/119] refac --- backend/open_webui/routers/channels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 22feb1b8f6..487899fccf 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -305,7 +305,7 @@ async def create_new_channel( detail=ERROR_MESSAGES.UNAUTHORIZED, ) - form_data.access_grants = filter_allowed_access_grants( + form_data.access_grants = await filter_allowed_access_grants( request.app.state.config.USER_PERMISSIONS, user.id, user.role, @@ -643,7 +643,7 @@ async def update_channel_by_id( if channel.user_id != user.id and user.role != 'admin': raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()) - form_data.access_grants = filter_allowed_access_grants( + form_data.access_grants = await filter_allowed_access_grants( request.app.state.config.USER_PERMISSIONS, user.id, user.role, From b9fc3f367ae739a0c9364417c1be31681e0b237b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 15:47:32 +0900 Subject: [PATCH 109/119] refac --- backend/open_webui/retrieval/utils.py | 117 +++++++++++++++++++++++- backend/open_webui/routers/retrieval.py | 38 +------- 2 files changed, 117 insertions(+), 38 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 93ba72ce13..cafb8fe4f0 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -83,11 +83,120 @@ def get_loader(request, url: str): ) +def build_loader_from_config(request): + """Build a Loader instance with the admin's configured extraction engine settings.""" + from open_webui.retrieval.loaders.main import Loader + + config = request.app.state.config + return Loader( + engine=config.CONTENT_EXTRACTION_ENGINE, + DATALAB_MARKER_API_KEY=config.DATALAB_MARKER_API_KEY, + DATALAB_MARKER_API_BASE_URL=config.DATALAB_MARKER_API_BASE_URL, + DATALAB_MARKER_ADDITIONAL_CONFIG=config.DATALAB_MARKER_ADDITIONAL_CONFIG, + DATALAB_MARKER_SKIP_CACHE=config.DATALAB_MARKER_SKIP_CACHE, + DATALAB_MARKER_FORCE_OCR=config.DATALAB_MARKER_FORCE_OCR, + DATALAB_MARKER_PAGINATE=config.DATALAB_MARKER_PAGINATE, + DATALAB_MARKER_STRIP_EXISTING_OCR=config.DATALAB_MARKER_STRIP_EXISTING_OCR, + DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION=config.DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION, + DATALAB_MARKER_FORMAT_LINES=config.DATALAB_MARKER_FORMAT_LINES, + DATALAB_MARKER_USE_LLM=config.DATALAB_MARKER_USE_LLM, + DATALAB_MARKER_OUTPUT_FORMAT=config.DATALAB_MARKER_OUTPUT_FORMAT, + EXTERNAL_DOCUMENT_LOADER_URL=config.EXTERNAL_DOCUMENT_LOADER_URL, + EXTERNAL_DOCUMENT_LOADER_API_KEY=config.EXTERNAL_DOCUMENT_LOADER_API_KEY, + TIKA_SERVER_URL=config.TIKA_SERVER_URL, + DOCLING_SERVER_URL=config.DOCLING_SERVER_URL, + DOCLING_API_KEY=config.DOCLING_API_KEY, + DOCLING_PARAMS=config.DOCLING_PARAMS, + PDF_EXTRACT_IMAGES=config.PDF_EXTRACT_IMAGES, + PDF_LOADER_MODE=config.PDF_LOADER_MODE, + DOCUMENT_INTELLIGENCE_ENDPOINT=config.DOCUMENT_INTELLIGENCE_ENDPOINT, + DOCUMENT_INTELLIGENCE_KEY=config.DOCUMENT_INTELLIGENCE_KEY, + DOCUMENT_INTELLIGENCE_MODEL=config.DOCUMENT_INTELLIGENCE_MODEL, + MISTRAL_OCR_API_BASE_URL=config.MISTRAL_OCR_API_BASE_URL, + MISTRAL_OCR_API_KEY=config.MISTRAL_OCR_API_KEY, + MINERU_API_MODE=config.MINERU_API_MODE, + MINERU_API_URL=config.MINERU_API_URL, + MINERU_API_KEY=config.MINERU_API_KEY, + MINERU_API_TIMEOUT=config.MINERU_API_TIMEOUT, + MINERU_PARAMS=config.MINERU_PARAMS, + ) + + +def _extract_text_from_binary_response( + request, response: requests.Response, url: str +) -> tuple[str, list]: + """Download response body to a temp file and extract text using the Loader pipeline.""" + import mimetypes + import tempfile + import urllib.parse + + content_type = response.headers.get('Content-Type', '').split(';')[0].strip() + + # Derive filename from URL path, falling back to Content-Disposition or mime guess + url_path = urllib.parse.urlparse(url).path + filename = os.path.basename(url_path) if url_path else '' + + if not filename or '.' not in filename: + # Try Content-Disposition header + cd = response.headers.get('Content-Disposition', '') + if 'filename=' in cd: + filename = cd.split('filename=')[-1].strip('"\'') + + if not filename or '.' not in filename: + ext = mimetypes.guess_extension(content_type) or '' + filename = f'download{ext}' + + suffix = '.' + filename.split('.')[-1].lower() if '.' in filename else '' + + with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: + tmp.write(response.content) + tmp_path = tmp.name + + try: + loader = build_loader_from_config(request) + docs = loader.load(filename, content_type, tmp_path) + for doc in docs: + doc.metadata['source'] = url + content = ' '.join([doc.page_content for doc in docs]) + return content, docs + finally: + os.remove(tmp_path) + + +def _is_text_content_type(content_type: str) -> bool: + """Return True if the content type should be handled by the web loader.""" + ct = content_type.split(';')[0].strip().lower() + if ct.startswith('text/'): + return True + if any(t in ct for t in ['xml', 'json', 'javascript']): + return True + return not ct # empty / missing → assume HTML + + def get_content_from_url(request, url: str) -> str: - loader = get_loader(request, url) - docs = loader.load() - content = ' '.join([doc.page_content for doc in docs]) - return content, docs + # Streamed GET to check Content-Type without downloading the body. + try: + response = requests.get(url, stream=True, timeout=30) + response.raise_for_status() + content_type = response.headers.get('Content-Type', '') + except Exception: + content_type = '' + response = None + + # Text / HTML / unknown — use the configured web loader + if response is None or _is_text_content_type(content_type): + if response is not None: + response.close() + loader = get_loader(request, url) + docs = loader.load() + content = ' '.join([doc.page_content for doc in docs]) + return content, docs + + # Binary content (PDF, DOCX, XLSX, PPTX, etc.) — download and extract + try: + return _extract_text_from_binary_response(request, response, url) + finally: + response.close() CHUNK_HASH_KEY = '_chunk_hash' diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index ee8a9007fe..fea00143e6 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -48,7 +48,7 @@ from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT # Document loaders -from open_webui.retrieval.loaders.main import Loader + from open_webui.retrieval.loaders.youtube import YoutubeLoader # Web search engines @@ -82,6 +82,7 @@ from open_webui.retrieval.web.yandex import search_yandex from open_webui.retrieval.web.ydc import search_youcom from open_webui.retrieval.utils import ( + build_loader_from_config, filter_accessible_collections, get_content_from_url, get_embedding_function, @@ -1623,39 +1624,8 @@ async def process_file( file_path = file.path if file_path: file_path = await asyncio.to_thread(Storage.get_file, file_path) - loader = Loader( - engine=request.app.state.config.CONTENT_EXTRACTION_ENGINE, - user=user, - DATALAB_MARKER_API_KEY=request.app.state.config.DATALAB_MARKER_API_KEY, - DATALAB_MARKER_API_BASE_URL=request.app.state.config.DATALAB_MARKER_API_BASE_URL, - DATALAB_MARKER_ADDITIONAL_CONFIG=request.app.state.config.DATALAB_MARKER_ADDITIONAL_CONFIG, - DATALAB_MARKER_SKIP_CACHE=request.app.state.config.DATALAB_MARKER_SKIP_CACHE, - DATALAB_MARKER_FORCE_OCR=request.app.state.config.DATALAB_MARKER_FORCE_OCR, - DATALAB_MARKER_PAGINATE=request.app.state.config.DATALAB_MARKER_PAGINATE, - DATALAB_MARKER_STRIP_EXISTING_OCR=request.app.state.config.DATALAB_MARKER_STRIP_EXISTING_OCR, - DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION=request.app.state.config.DATALAB_MARKER_DISABLE_IMAGE_EXTRACTION, - DATALAB_MARKER_FORMAT_LINES=request.app.state.config.DATALAB_MARKER_FORMAT_LINES, - DATALAB_MARKER_USE_LLM=request.app.state.config.DATALAB_MARKER_USE_LLM, - DATALAB_MARKER_OUTPUT_FORMAT=request.app.state.config.DATALAB_MARKER_OUTPUT_FORMAT, - EXTERNAL_DOCUMENT_LOADER_URL=request.app.state.config.EXTERNAL_DOCUMENT_LOADER_URL, - EXTERNAL_DOCUMENT_LOADER_API_KEY=request.app.state.config.EXTERNAL_DOCUMENT_LOADER_API_KEY, - TIKA_SERVER_URL=request.app.state.config.TIKA_SERVER_URL, - DOCLING_SERVER_URL=request.app.state.config.DOCLING_SERVER_URL, - DOCLING_API_KEY=request.app.state.config.DOCLING_API_KEY, - DOCLING_PARAMS=request.app.state.config.DOCLING_PARAMS, - PDF_EXTRACT_IMAGES=request.app.state.config.PDF_EXTRACT_IMAGES, - PDF_LOADER_MODE=request.app.state.config.PDF_LOADER_MODE, - DOCUMENT_INTELLIGENCE_ENDPOINT=request.app.state.config.DOCUMENT_INTELLIGENCE_ENDPOINT, - DOCUMENT_INTELLIGENCE_KEY=request.app.state.config.DOCUMENT_INTELLIGENCE_KEY, - DOCUMENT_INTELLIGENCE_MODEL=request.app.state.config.DOCUMENT_INTELLIGENCE_MODEL, - MISTRAL_OCR_API_BASE_URL=request.app.state.config.MISTRAL_OCR_API_BASE_URL, - MISTRAL_OCR_API_KEY=request.app.state.config.MISTRAL_OCR_API_KEY, - MINERU_API_MODE=request.app.state.config.MINERU_API_MODE, - MINERU_API_URL=request.app.state.config.MINERU_API_URL, - MINERU_API_KEY=request.app.state.config.MINERU_API_KEY, - MINERU_API_TIMEOUT=request.app.state.config.MINERU_API_TIMEOUT, - MINERU_PARAMS=request.app.state.config.MINERU_PARAMS, - ) + loader = build_loader_from_config(request) + loader.user = user docs = await loader.aload(file.filename, file.meta.get('content_type'), file_path) docs = [ From 6cc799b1bbc77a3ec3d1484abd0d95b41a5baca7 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 15:52:00 +0900 Subject: [PATCH 110/119] chore: format --- CHANGELOG.md | 2 +- backend/open_webui/internal/db.py | 20 +++++------ backend/open_webui/models/calendar.py | 2 -- backend/open_webui/retrieval/utils.py | 4 +-- backend/open_webui/routers/calendar.py | 4 +-- backend/open_webui/routers/configs.py | 16 ++++++--- backend/open_webui/routers/functions.py | 4 ++- backend/open_webui/routers/tools.py | 4 ++- backend/open_webui/utils/files.py | 36 ++++++++----------- backend/open_webui/utils/tools.py | 8 +++-- .../calendar/CalendarSidebar.svelte | 11 +++--- src/lib/components/chat/Chat.svelte | 13 ++----- src/lib/components/chat/MessageInput.svelte | 4 ++- src/lib/i18n/locales/ar-BH/translation.json | 23 ++++++++++++ src/lib/i18n/locales/ar/translation.json | 23 ++++++++++++ src/lib/i18n/locales/az-AZ/translation.json | 19 ++++++++++ src/lib/i18n/locales/bg-BG/translation.json | 19 ++++++++++ src/lib/i18n/locales/bn-BD/translation.json | 19 ++++++++++ src/lib/i18n/locales/bo-TB/translation.json | 18 ++++++++++ src/lib/i18n/locales/bs-BA/translation.json | 20 +++++++++++ src/lib/i18n/locales/ca-ES/translation.json | 20 +++++++++++ src/lib/i18n/locales/ceb-PH/translation.json | 19 ++++++++++ src/lib/i18n/locales/cs-CZ/translation.json | 21 +++++++++++ src/lib/i18n/locales/da-DK/translation.json | 19 ++++++++++ src/lib/i18n/locales/de-DE/translation.json | 19 ++++++++++ src/lib/i18n/locales/dg-DG/translation.json | 19 ++++++++++ src/lib/i18n/locales/el-GR/translation.json | 19 ++++++++++ src/lib/i18n/locales/en-GB/translation.json | 19 ++++++++++ src/lib/i18n/locales/en-US/translation.json | 19 ++++++++++ src/lib/i18n/locales/es-ES/translation.json | 20 +++++++++++ src/lib/i18n/locales/et-EE/translation.json | 19 ++++++++++ src/lib/i18n/locales/eu-ES/translation.json | 19 ++++++++++ src/lib/i18n/locales/fa-IR/translation.json | 19 ++++++++++ src/lib/i18n/locales/fi-FI/translation.json | 19 ++++++++++ src/lib/i18n/locales/fr-CA/translation.json | 20 +++++++++++ src/lib/i18n/locales/fr-FR/translation.json | 20 +++++++++++ src/lib/i18n/locales/gl-ES/translation.json | 19 ++++++++++ src/lib/i18n/locales/he-IL/translation.json | 20 +++++++++++ src/lib/i18n/locales/hi-IN/translation.json | 19 ++++++++++ src/lib/i18n/locales/hr-HR/translation.json | 20 +++++++++++ src/lib/i18n/locales/hu-HU/translation.json | 19 ++++++++++ src/lib/i18n/locales/id-ID/translation.json | 18 ++++++++++ src/lib/i18n/locales/ie-GA/translation.json | 19 ++++++++++ src/lib/i18n/locales/it-IT/translation.json | 20 +++++++++++ src/lib/i18n/locales/ja-JP/translation.json | 18 ++++++++++ src/lib/i18n/locales/ka-GE/translation.json | 19 ++++++++++ src/lib/i18n/locales/kab-DZ/translation.json | 19 ++++++++++ src/lib/i18n/locales/ko-KR/translation.json | 18 ++++++++++ src/lib/i18n/locales/lt-LT/translation.json | 21 +++++++++++ src/lib/i18n/locales/lv-LV/translation.json | 20 +++++++++++ src/lib/i18n/locales/ms-MY/translation.json | 18 ++++++++++ src/lib/i18n/locales/nb-NO/translation.json | 19 ++++++++++ src/lib/i18n/locales/nl-NL/translation.json | 19 ++++++++++ src/lib/i18n/locales/pa-IN/translation.json | 19 ++++++++++ src/lib/i18n/locales/pl-PL/translation.json | 21 +++++++++++ src/lib/i18n/locales/pt-BR/translation.json | 20 +++++++++++ src/lib/i18n/locales/pt-PT/translation.json | 20 +++++++++++ src/lib/i18n/locales/ro-RO/translation.json | 20 +++++++++++ src/lib/i18n/locales/ru-RU/translation.json | 21 +++++++++++ src/lib/i18n/locales/sk-SK/translation.json | 21 +++++++++++ src/lib/i18n/locales/sr-RS/translation.json | 20 +++++++++++ src/lib/i18n/locales/sv-SE/translation.json | 19 ++++++++++ src/lib/i18n/locales/ta-IN/translation.json | 19 ++++++++++ src/lib/i18n/locales/th-TH/translation.json | 18 ++++++++++ src/lib/i18n/locales/tk-TM/translation.json | 19 ++++++++++ src/lib/i18n/locales/tr-TR/translation.json | 19 ++++++++++ src/lib/i18n/locales/ug-CN/translation.json | 19 ++++++++++ src/lib/i18n/locales/uk-UA/translation.json | 21 +++++++++++ src/lib/i18n/locales/ur-PK/translation.json | 19 ++++++++++ .../i18n/locales/uz-Cyrl-UZ/translation.json | 19 ++++++++++ .../i18n/locales/uz-Latn-Uz/translation.json | 19 ++++++++++ src/lib/i18n/locales/vi-VN/translation.json | 18 ++++++++++ src/lib/i18n/locales/zh-CN/translation.json | 18 ++++++++++ src/lib/i18n/locales/zh-TW/translation.json | 18 ++++++++++ 74 files changed, 1244 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f6a27199..7d5f34d74f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -214,7 +214,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 📝 **Writing block parsing reliability.** ":::writing" blocks now parse more reliably when headers or extra inline text are present, preventing malformed rendering and duplicate output artifacts. [#23174](https://github.com/open-webui/open-webui/issues/23174) - 🧾 **Code block line break reliability.** Blank lines in submitted code blocks are now preserved more reliably instead of being collapsed. [Commit](https://github.com/open-webui/open-webui/commit/1be9627dd27ffe75957729a4a0d1682a98684f01), [#20302](https://github.com/open-webui/open-webui/issues/20302), [#23451](https://github.com/open-webui/open-webui/pull/23451) - ✂️ **Citation spacing cleanup.** When citations are disabled for a model, citation markers and their leftover spacing are now removed together so punctuation and copied text remain cleanly formatted. [#23141](https://github.com/open-webui/open-webui/issues/23141) -- 🧰 **Pipe tool access.** Pipe functions now receive built-in and MCP tools in __tools__, so tools like Web Search and code execution are available when enabled. [#23365](https://github.com/open-webui/open-webui/issues/23365) +- 🧰 **Pipe tool access.** Pipe functions now receive built-in and MCP tools in **tools**, so tools like Web Search and code execution are available when enabled. [#23365](https://github.com/open-webui/open-webui/issues/23365) - 📚 **Batch file processing database handling.** Batch knowledge file processing now consistently uses the active database session, preventing failures caused by missing database context during file ownership checks and update writes. [#23137](https://github.com/open-webui/open-webui/issues/23137) - ⚙️ **Default model parameter loading.** The "DEFAULT_MODEL_PARAMS" environment variable is now parsed and applied correctly, so default generation settings are honored reliably without being ignored at startup. [#23223](https://github.com/open-webui/open-webui/pull/23223) - 🔧 **Web search settings save reliability.** Saving web search configuration now works without server errors, so administrators can update "WEB_FETCH_MAX_CONTENT_LENGTH" and related retrieval settings successfully from the admin interface. [Commit](https://github.com/open-webui/open-webui/commit/36d02aa1477aa1b4e7fb59d022f99693ebfa8667), [#23127](https://github.com/open-webui/open-webui/issues/23127) diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index e3b4a110cd..25aa94591b 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -56,10 +56,7 @@ def extract_ssl_mode_from_url(url: str) -> tuple[str, str | None]: Non-PostgreSQL URLs are returned unchanged with ``ssl_mode=None``. """ - if not url or not any( - url.startswith(prefix) - for prefix in ('postgresql://', 'postgresql+', 'postgres://') - ): + if not url or not any(url.startswith(prefix) for prefix in ('postgresql://', 'postgresql+', 'postgres://')): return url, None parsed = urlparse(url) @@ -126,7 +123,6 @@ def reattach_ssl_mode_to_url(url_without_ssl: str, ssl_mode: str | None) -> str: return f'{url_without_ssl}{separator}sslmode={ssl_mode}' - class JSONField(types.TypeDecorator): impl = types.Text cache_ok = True @@ -188,7 +184,9 @@ if ENABLE_DB_MIGRATIONS: DATABASE_URL_WITHOUT_SSL, DATABASE_SSL_MODE = extract_ssl_mode_from_url(DATABASE_URL) # For psycopg2 (sync engine), re-append sslmode=. -SQLALCHEMY_DATABASE_URL = reattach_ssl_mode_to_url(DATABASE_URL_WITHOUT_SSL, DATABASE_SSL_MODE) if DATABASE_SSL_MODE else DATABASE_URL +SQLALCHEMY_DATABASE_URL = ( + reattach_ssl_mode_to_url(DATABASE_URL_WITHOUT_SSL, DATABASE_SSL_MODE) if DATABASE_SSL_MODE else DATABASE_URL +) def _make_async_url(url: str) -> str: @@ -332,15 +330,13 @@ get_db = contextmanager(get_session) # ============================================================ # Use the SSL-stripped URL for asyncpg — SSL is injected via connect_args. -ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url(DATABASE_URL_WITHOUT_SSL if DATABASE_SSL_MODE else SQLALCHEMY_DATABASE_URL) +ASYNC_SQLALCHEMY_DATABASE_URL = _make_async_url( + DATABASE_URL_WITHOUT_SSL if DATABASE_SSL_MODE else SQLALCHEMY_DATABASE_URL +) if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL: # Generous default — async coroutines + no session sharing = high connection demand. - _sqlite_pool_size = ( - DATABASE_POOL_SIZE - if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0 - else 512 - ) + _sqlite_pool_size = DATABASE_POOL_SIZE if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0 else 512 async_engine = create_async_engine( ASYNC_SQLALCHEMY_DATABASE_URL, connect_args={'check_same_thread': False}, diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index dbb070013e..47f0a6f722 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -307,8 +307,6 @@ class CalendarTable: cal = result.scalars().first() return await self._to_calendar_model(cal, db=db) if cal else None - - async def insert_new_calendar( self, user_id: str, form_data: CalendarForm, db: Optional[AsyncSession] = None ) -> Optional[CalendarModel]: diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index cafb8fe4f0..b9bfcc12c8 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -122,9 +122,7 @@ def build_loader_from_config(request): ) -def _extract_text_from_binary_response( - request, response: requests.Response, url: str -) -> tuple[str, list]: +def _extract_text_from_binary_response(request, response: requests.Response, url: str) -> tuple[str, list]: """Download response body to a temp file and extract text using the Loader pipeline.""" import mimetypes import tempfile diff --git a/backend/open_webui/routers/calendar.py b/backend/open_webui/routers/calendar.py index 152b932234..c95888ebfa 100644 --- a/backend/open_webui/routers/calendar.py +++ b/backend/open_webui/routers/calendar.py @@ -55,9 +55,7 @@ async def _user_has_automations(request: Request, user) -> bool: return False if user.role == 'admin': return True - return await has_permission( - user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS - ) + return await has_permission(user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS) async def _check_calendar_access(calendar_id: str, user: UserModel, permission: str = 'write') -> CalendarModel: diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 68e1d129dc..02b16d8e5b 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -293,7 +293,9 @@ async def verify_terminal_server_connection( ) as session: # Orchestrators expose a policies API; plain terminals don't. try: - async with session.get(f'{base_url}/api/v1/policies', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + f'{base_url}/api/v1/policies', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.ok: return {'status': True, 'type': 'orchestrator'} except Exception: @@ -301,7 +303,9 @@ async def verify_terminal_server_connection( # Fall back to open-terminal config endpoint. try: - async with session.get(f'{base_url}/api/config', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + f'{base_url}/api/config', headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.ok: return {'status': True, 'type': 'terminal'} except Exception: @@ -342,7 +346,9 @@ async def put_terminal_server_policy( timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) as session: policy_url = f'{base_url}/api/v1/policies/{form_data.policy_id}' - async with session.put(policy_url, headers=headers, json=form_data.policy_data, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.put( + policy_url, headers=headers, json=form_data.policy_data, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.ok: return await resp.json() detail = await resp.text() @@ -369,7 +375,9 @@ async def verify_tool_servers_config(request: Request, form_data: ToolServerConn trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) as session: - async with session.get(discovery_url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as oauth_server_metadata_response: + async with session.get( + discovery_url, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as oauth_server_metadata_response: if oauth_server_metadata_response.status == 200: try: oauth_server_metadata = OAuthMetadata.model_validate( diff --git a/backend/open_webui/routers/functions.py b/backend/open_webui/routers/functions.py index baec1f0870..f40cd1ab82 100644 --- a/backend/open_webui/routers/functions.py +++ b/backend/open_webui/routers/functions.py @@ -117,7 +117,9 @@ async def load_function_from_url(request: Request, form_data: LoadUrlForm, user= async with aiohttp.ClientSession( trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) ) as session: - async with session.get(url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.status != 200: raise HTTPException(status_code=resp.status, detail='Failed to fetch the function') data = await resp.text() diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index 4c3e77e566..04d845c3de 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -274,7 +274,9 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe async with aiohttp.ClientSession( trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) ) as session: - async with session.get(url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + url, headers={'Content-Type': 'application/json'}, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.status != 200: raise HTTPException(status_code=resp.status, detail='Failed to fetch the tool') data = await resp.text() diff --git a/backend/open_webui/utils/files.py b/backend/open_webui/utils/files.py index 7d0d9da2c2..8149987fe4 100644 --- a/backend/open_webui/utils/files.py +++ b/backend/open_webui/utils/files.py @@ -34,19 +34,19 @@ MARKDOWN_IMAGE_URL_PATTERN = re.compile(r'!\[(.*?)\]\((.+?)\)', re.IGNORECASE) # Extension-based MIME fallback, only used when ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK is True. _IMAGE_MIME_FALLBACK = { - ".webp": "image/webp", - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".svg": "image/svg+xml", - ".bmp": "image/bmp", - ".tiff": "image/tiff", - ".tif": "image/tiff", - ".ico": "image/x-icon", - ".heic": "image/heic", - ".heif": "image/heif", - ".avif": "image/avif", + '.webp': 'image/webp', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.bmp': 'image/bmp', + '.tiff': 'image/tiff', + '.tif': 'image/tiff', + '.ico': 'image/x-icon', + '.heic': 'image/heic', + '.heif': 'image/heif', + '.avif': 'image/avif', } @@ -75,10 +75,7 @@ async def get_image_base64_from_url(url: str) -> Optional[str]: if file_path.is_file(): with open(file_path, 'rb') as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - content_type = ( - mimetypes.guess_type(file_path.name)[0] - or (file.meta or {}).get('content_type') - ) + content_type = mimetypes.guess_type(file_path.name)[0] or (file.meta or {}).get('content_type') if not content_type and ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK: content_type = _IMAGE_MIME_FALLBACK.get(file_path.suffix.lower()) if not content_type: @@ -204,10 +201,7 @@ async def get_image_base64_from_file_id(id: str) -> Optional[str]: if file_path.is_file(): with open(file_path, 'rb') as image_file: encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - content_type = ( - mimetypes.guess_type(file_path.name)[0] - or (file.meta or {}).get('content_type') - ) + content_type = mimetypes.guess_type(file_path.name)[0] or (file.meta or {}).get('content_type') if not content_type and ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK: content_type = _IMAGE_MIME_FALLBACK.get(file_path.suffix.lower()) if not content_type: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 3f4eac7e91..9f3ab0bce4 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -908,7 +908,9 @@ async def get_terminal_cwd( timeout=aiohttp.ClientTimeout(total=5), trust_env=True, ) as session: - async with session.get(cwd_url, headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + cwd_url, headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.status == 200: data = await resp.json() return data.get('cwd') @@ -943,7 +945,9 @@ async def get_terminal_system_prompt( return None # 2. Fetch system prompt - async with session.get(f'{base}/system', headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + async with session.get( + f'{base}/system', headers=headers, cookies=cookies or {}, ssl=AIOHTTP_CLIENT_SESSION_SSL + ) as resp: if resp.status == 200: data = await resp.json() return data.get('prompt') diff --git a/src/lib/components/calendar/CalendarSidebar.svelte b/src/lib/components/calendar/CalendarSidebar.svelte index 76d762760a..d3ea51a472 100644 --- a/src/lib/components/calendar/CalendarSidebar.svelte +++ b/src/lib/components/calendar/CalendarSidebar.svelte @@ -94,7 +94,10 @@ @@ -219,11 +222,7 @@ stroke="currentColor" class="size-3" > - + {/if} diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index fbd91e512c..03af994a68 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -757,10 +757,7 @@ const selectedFolderSubscribe = selectedFolder.subscribe(async (folder) => { await tick(); - if ( - folder?.data?.model_ids && - !equal(selectedModels, folder.data.model_ids) - ) { + if (folder?.data?.model_ids && !equal(selectedModels, folder.data.model_ids)) { selectedModels = folder.data.model_ids; console.log('Set selectedModels from folder data:', selectedModels); @@ -1836,8 +1833,7 @@ ); chatFiles = chatFiles.filter( // Remove duplicates - (item, index, array) => - array.findIndex((i) => equal(i, item)) === index + (item, index, array) => array.findIndex((i) => equal(i, item)) === index ); // Create user message @@ -2176,10 +2172,7 @@ ) ); // Remove duplicates - files = files.filter( - (item, index, array) => - array.findIndex((i) => equal(i, item)) === index - ); + files = files.filter((item, index, array) => array.findIndex((i) => equal(i, item)) === index); scrollToBottom(); eventTarget.dispatchEvent( diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index aeb96af5b0..11cd749987 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1941,7 +1941,9 @@ {#if !history?.currentId || history.messages[history.currentId]?.done == true} - {@const hasDirectToolServerAccess = $_user?.role === 'admin' || ($_user?.permissions?.features?.direct_tool_servers ?? true)} + {@const hasDirectToolServerAccess = + $_user?.role === 'admin' || + ($_user?.permissions?.features?.direct_tool_servers ?? true)} {#if terminalCapableModels.length > 0 && (($terminalServers ?? []).some((t) => t.id) || (hasDirectToolServerAccess && (($terminalServers ?? []).some((t) => !t.id) || ($settings?.terminalServers ?? []).some((s) => s.url))))} {/if} diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index 1b4ff02105..13e9aed4e9 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -37,8 +37,13 @@ "{{user}}'s Chats": "دردشات {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} مطلوب", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -206,6 +211,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -280,6 +286,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -417,6 +424,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "اتصالات", @@ -529,6 +537,8 @@ "Delete All Chats": "حذف جميع الدردشات", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "حذف المحادثه.", "Delete chat?": "", "Delete Event": "", @@ -890,6 +900,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "فشل في إنشاء مفتاح API.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1629,6 +1640,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "سجل صوت", "Redirecting you to Open WebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ", @@ -1652,6 +1664,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "إزالة", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1902,7 +1915,15 @@ "Start a new conversation": "", "Start of the channel": "بداية القناة", "Start Tag": "", + "Starting in {{count}} minutes_zero": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_two": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2015,6 +2036,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "شرح شامل", "Thought": "", @@ -2101,6 +2123,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ar/translation.json b/src/lib/i18n/locales/ar/translation.json index 49a3c5be10..3eb53e68bd 100644 --- a/src/lib/i18n/locales/ar/translation.json +++ b/src/lib/i18n/locales/ar/translation.json @@ -37,8 +37,13 @@ "{{user}}'s Chats": "محادثات المستخدم {{user}}", "{{webUIName}} Backend Required": "يتطلب الخلفية الخاصة بـ {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*معرّف/معرّفات عقدة الموجه مطلوبة لتوليد الصور", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "يتوفر الآن إصدار جديد (v{{LATEST_VERSION}}).", @@ -206,6 +211,7 @@ "Ask a question": "اطرح سؤالاً", "Assistant": "المساعد", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -280,6 +286,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "التقويم", + "Calendar deleted": "", "Calendars": "", "Call": "مكالمة", "Call feature is not supported when using Web STT engine": "ميزة الاتصال غير مدعومة عند استخدام محرك Web STT", @@ -417,6 +424,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "الاتصالات", @@ -529,6 +537,8 @@ "Delete All Chats": "حذف جميع الدردشات", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "حذف المحادثه.", "Delete chat?": "هل تريد حذف المحادثة؟", "Delete Event": "", @@ -890,6 +900,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "فشل في إنشاء مفتاح API.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1629,6 +1640,7 @@ "Reasoning Effort": "جهد الاستدلال", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "سجل صوت", "Redirecting you to Open WebUI Community": "OpenWebUI إعادة توجيهك إلى مجتمع ", @@ -1652,6 +1664,7 @@ "Relevance": "الصلة", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "إزالة", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1902,7 +1915,15 @@ "Start a new conversation": "", "Start of the channel": "بداية القناة", "Start Tag": "", + "Starting in {{count}} minutes_zero": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_two": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2015,6 +2036,7 @@ "This will delete {{NAME}} and all its contents.": "هذا سيحذف {{NAME}} وكل محتوياته.", "This will delete all models including custom models": "هذا سيحذف جميع النماذج بما في ذلك النماذج المخصصة", "This will delete all models including custom models and cannot be undone.": "هذا سيحذف جميع النماذج بما في ذلك المخصصة ولا يمكن التراجع عن هذا الإجراء.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "هذا سيؤدي إلى إعادة تعيين قاعدة المعرفة ومزامنة جميع الملفات. هل ترغب في المتابعة؟", "Thorough explanation": "شرح شامل", "Thought": "", @@ -2101,6 +2123,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "اكشف الأسرار", "Unpin": "إزالة التثبيت", + "Unpin from Sidebar": "", "Unravel secrets": "فكّ الأسرار", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/az-AZ/translation.json b/src/lib/i18n/locales/az-AZ/translation.json index 9e316d538d..8eec5e5732 100644 --- a/src/lib/i18n/locales/az-AZ/translation.json +++ b/src/lib/i18n/locales/az-AZ/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} adlı istifadəçinin söhbətləri", "{{webUIName}} Backend Required": "{{webUIName}} üçün Backend tələb olunur", "*Prompt node ID(s) are required for image generation": "*Şəkil yaradılması üçün sorğu (prompt) qovşaq ID-ləri tələb olunur", + "1 hour before": "", "1 Source": "1 Mənbə", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1 dəq əvvəl", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "İnsanların üzv kimi qoşulduğu əməkdaşlıq kanalı", "A discussion channel where access is controlled by groups and permissions": "Girişin qruplar və icazələrlə idarə olunduğu müzakirə kanalı", "A new version (v{{LATEST_VERSION}}) is now available.": "Yeni versiya (v{{LATEST_VERSION}}) artıq mövcuddur.", @@ -202,6 +207,7 @@ "Ask a question": "Sual verin", "Assistant": "Köməkçi", "Async Embedding Processing": "Asinxron Yerləşdirmə (Embedding) Emalı", + "At time of event": "", "Attach File From Knowledge": "Bilik bazasından fayl əlavə et", "Attach Files": "", "Attach Knowledge": "Bilik əlavə et", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Veb Yükləyicidən Yan Keç", "Cache Base Model List": "Əsas Model Siyahısını Keşlə", "Calendar": "Təqvim", + "Calendar deleted": "", "Calendars": "", "Call": "Zəng", "Call feature is not supported when using Web STT engine": "Veb STT mühərriki istifadə edildikdə zəng funksiyası dəstəklənmir", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Öz OpenAPI uyğun xarici alət serverlərinizə qoşulun.", "Connected ({{type}})": "", "Connection failed": "Bağlantı uğursuz oldu", + "Connection lost. Reconnecting...": "", "Connection successful": "Bağlantı uğurludur", "Connection Type": "Bağlantı növü", "Connections": "Bağlantılar", @@ -525,6 +533,8 @@ "Delete All Chats": "Bütün çatları sil", "Delete all contents inside this folder": "Bu qovluğun daxilindəki bütün məzmunu sil", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Çatı sil", "Delete chat?": "Çat silinsin?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "{{URL}} terminal serverinə qoşulmaq mümkün olmadı", "Failed to copy link": "Link kopyalanmadı", "Failed to create API Key.": "API açarı yaradılmadı.", + "Failed to delete calendar": "", "Failed to delete note": "Qeyd silinmədi", "Failed to download image": "Şəkil yüklənmədi", "Failed to extract content from the file: {{error}}": "Fayldan məzmun çıxarıla bilmədi: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Mühakimə səyi", "Reasoning Tags": "Mühakimə etiketləri", "Recently Used": "", + "Reconnected": "", "Record": "Yaz (səs)", "Record voice": "Səsi yaz", "Redirecting you to Open WebUI Community": "Open WebUI İcmasına yönləndirilirsiniz", @@ -1648,6 +1660,7 @@ "Relevance": "Uyğunluq", "Relevance Threshold": "Uyğunluq həddi", "Remember Dismissal": "İmtinanı yadda saxla", + "Reminder": "", "Remove": "Çıxar", "Remove {{MODELID}} from list.": "{{MODELID}} siyahıdan çıxarılsın.", "Remove action": "Əməliyyatı çıxar", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Yeni söhbətə başlayın", "Start of the channel": "Kanalın başlanğıcı", "Start Tag": "Start Tag", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Starting kernel...", + "Starting now": "", "State": "", "Status": "Status", "Status cleared successfully": "Status uğurla təmizləndi", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Bu, {{NAME}} adlı elementi və onun bütün məzmununu siləcək.", "This will delete all models including custom models": "Bu, fərdi modellər də daxil olmaqla bütün modelləri siləcək", "This will delete all models including custom models and cannot be undone.": "Bu, fərdi modellər də daxil olmaqla bütün modelləri siləcək və geri qaytarıla bilməz.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Bu, bilik bazasını sıfırlayacaq və bütün faylları sinxronizasiya edəcək. Davam etmək istəyirsiniz?", "Thorough explanation": "Ətraflı izahat", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} sonra yaddaşdan silinir", "Unlock mysteries": "Sirrləri açın", "Unpin": "Sabitlənmişdən çıxar", + "Unpin from Sidebar": "", "Unravel secrets": "Gizlinləri üzə çıxarın", "Unshare Chat": "Çatı paylaşımı dayandır", "Unsupported file type.": "Dəstəklənməyən fayl növü.", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index 51dbe73be0..685debf883 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}'s чатове", "{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд", "*Prompt node ID(s) are required for image generation": "*Идентификатор(ите) на възел-а се изисква(т) за генериране на изображения", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Вече е налична нова версия (v{{LATEST_VERSION}}).", @@ -202,6 +207,7 @@ "Ask a question": "Задайте въпрос", "Assistant": "Асистент", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Календар", + "Calendar deleted": "", "Calendars": "", "Call": "Обаждане", "Call feature is not supported when using Web STT engine": "Функцията за обаждане не се поддържа при използването на Web STT двигател", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Връзки", @@ -525,6 +533,8 @@ "Delete All Chats": "Изтриване на всички чатове", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Изтриване на Чат", "Delete chat?": "Изтриване на чата?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Неуспешно създаване на API ключ.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Усилие за разсъждение", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Запиши", "Record voice": "Записване на глас", "Redirecting you to Open WebUI Community": "Пренасочване към OpenWebUI общността", @@ -1648,6 +1660,7 @@ "Relevance": "Релевантност", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Изтриване", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Начало на канала", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Това ще изтрие {{NAME}} и цялото му съдържание.", "This will delete all models including custom models": "Това ще изтрие всички модели, включително персонализираните модели", "This will delete all models including custom models and cannot be undone.": "Това ще изтрие всички модели, включително персонализираните модели, и не може да бъде отменено.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Това ще нулира базата знания и ще синхронизира всички файлове. Желаете ли да продължите?", "Thorough explanation": "Подробно обяснение", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Разкрий мистерии", "Unpin": "Откачи", + "Unpin from Sidebar": "", "Unravel secrets": "Разгадай тайни", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index 5a1589d3b4..9437c8a347 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}র চ্যাটস", "{{webUIName}} Backend Required": "{{webUIName}} ব্যাকএন্ড আবশ্যক", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "কানেকশনগুলো", @@ -525,6 +533,8 @@ "Delete All Chats": "সব চ্যাট মুছে ফেলুন", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "চ্যাট মুছে ফেলুন", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "API Key তৈরি করা যায়নি।", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "ভয়েস রেকর্ড করুন", "Redirecting you to Open WebUI Community": "আপনাকে OpenWebUI কমিউনিটিতে পাঠানো হচ্ছে", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "রিমুভ করুন", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "চ্যানেলের শুরু", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "পুঙ্খানুপুঙ্খ ব্যাখ্যা", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/bo-TB/translation.json b/src/lib/i18n/locales/bo-TB/translation.json index c7f3716239..a65771c7b5 100644 --- a/src/lib/i18n/locales/bo-TB/translation.json +++ b/src/lib/i18n/locales/bo-TB/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "{{user}} ཡི་ཁ་བརྡ།", "{{webUIName}} Backend Required": "{{webUIName}} རྒྱབ་སྣེ་དགོས།", "*Prompt node ID(s) are required for image generation": "*པར་བཟོའི་ཆེད་དུ་འགུལ་སློང་མདུད་ཚེག་གི་ ID(s) དགོས།", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "པར་གཞི་གསར་པ། (v{{LATEST_VERSION}}) ད་ལྟ་ཡོད།", @@ -201,6 +206,7 @@ "Ask a question": "དྲི་བ་ཞིག་འདྲི་བ།", "Assistant": "ལག་རོགས་པ།", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -275,6 +281,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "ལོ་ཐོ།", + "Calendar deleted": "", "Calendars": "", "Call": "སྐད་འབོད།", "Call feature is not supported when using Web STT engine": "Web STT མ་ལག་སྤྱོད་སྐབས་སྐད་འབོད་ཀྱི་ཁྱད་ཆོས་ལ་རྒྱབ་སྐྱོར་མེད།", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "ཁྱེད་རང་གི་ OpenAPI དང་མཐུན་པའི་ཕྱི་རོལ་ལག་ཆའི་སར་བར་ལ་སྦྲེལ་བ།", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "སྦྲེལ་མཐུད།", @@ -524,6 +532,8 @@ "Delete All Chats": "ཁ་བརྡ་ཡོངས་རྫོགས་བསུབ་པ།", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "ཁ་བརྡ་བསུབ་པ།", "Delete chat?": "ཁ་བརྡ་བསུབ་པ།?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "API ལྡེ་མིག་བཟོ་མ་ཐུབ།", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "རྒྱུ་མཚན་འདྲེན་པའི་འབད་བརྩོན།", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "སྐད་སྒྲ་ཕབ་པ།", "Redirecting you to Open WebUI Community": "ཁྱེད་ Open WebUI སྤྱི་ཚོགས་ལ་ཁ་ཕྱོགས་སྒྱུར་བཞིན་པ།", @@ -1647,6 +1659,7 @@ "Relevance": "འབྲེལ་ཡོད་རང་བཞིན།", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "འདོར་བ།", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "", "Start of the channel": "རྒྱས་ལམ་འགོ་རིམ་", "Start Tag": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "འདིས་ {{NAME}} དང་ དེའི་ནང་དོན་ཡོངས་རྫོགས་ བསུབ་ངེས།", "This will delete all models including custom models": "འདིས་སྲོལ་བཟོས་དཔེ་དབྱིབས་ཚུད་པའི་དཔེ་དབྱིབས་ཡོངས་རྫོགས་བསུབ་ངེས།", "This will delete all models including custom models and cannot be undone.": "འདིས་སྲོལ་བཟོས་དཔེ་དབྱིབས་ཚུད་པའི་དཔེ་དབྱིབས་ཡོངས་རྫོགས་བསུབ་ངེས་པ་དང་ཕྱིར་ལྡོག་བྱེད་མི་ཐུབ།", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "འདིས་ཤེས་བྱའི་རྟེན་གཞི་སླར་སྒྲིག་བྱས་ནས་ཡིག་ཆ་ཡོངས་རྫོགས་མཉམ་སྡེབ་བྱེད་ངེས། ཁྱེད་མུ་མཐུད་འདོད་ཡོད་དམ།", "Thorough explanation": "འགྲེལ་བཤད་ཞིབ་ཚགས།", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "གསང་བ་གྲོལ་བ།", "Unpin": "ཕྱིར་འདོན།", + "Unpin from Sidebar": "", "Unravel secrets": "གསང་བ་གྲོལ་བ།", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/bs-BA/translation.json b/src/lib/i18n/locales/bs-BA/translation.json index 3316f8c4a7..d28abefd59 100644 --- a/src/lib/i18n/locales/bs-BA/translation.json +++ b/src/lib/i18n/locales/bs-BA/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Razgovori korisnika {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -203,6 +208,7 @@ "Ask a question": "Pitaj pitanje", "Assistant": "Asistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Prikazi znanje", @@ -277,6 +283,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Poziv", "Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "Konekcija nije uspjela", + "Connection lost. Reconnecting...": "", "Connection successful": "Konekcija uspjesna", "Connection Type": "Tip Konekcije", "Connections": "Povezivanja", @@ -526,6 +534,8 @@ "Delete All Chats": "Izbriši sve razgovore", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Izbriši razgovor", "Delete chat?": "", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Neuspješno stvaranje API ključa.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Snimanje glasa", "Redirecting you to Open WebUI Community": "Preusmjeravanje na OpenWebUI zajednicu", @@ -1649,6 +1661,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Ukloni", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Početak kanala", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "Detaljno objašnjenje", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index add558aebf..a3793e5e42 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Els xats de {{user}}", "{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari", "*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges", + "1 hour before": "", "1 Source": "1 font", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1m_time_ago", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Un canal de col·laboració on la gent s'uneix com a membres", "A discussion channel where access is controlled by groups and permissions": "Un canal de discussió on l'accés està controlat per grups i permisos", "A new version (v{{LATEST_VERSION}}) is now available.": "Hi ha una nova versió disponible (v{{LATEST_VERSION}}).", @@ -203,6 +208,7 @@ "Ask a question": "Fer una pregunta", "Assistant": "Assistent", "Async Embedding Processing": "Procés d'incrustat asíncron", + "At time of event": "", "Attach File From Knowledge": "Adjuntar arxiu del coneixement", "Attach Files": "Adjuntar arxius", "Attach Knowledge": "Adjuntar coneixement", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Ometre el càrregador web", "Cache Base Model List": "Llista de models base en memòria cau", "Calendar": "Calendari", + "Calendar deleted": "", "Calendars": "", "Call": "Trucada", "Call feature is not supported when using Web STT engine": "La funció de trucada no s'admet quan s'utilitza el motor Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Connecta als teus propis servidors d'eines externs compatibles amb OpenAPI", "Connected ({{type}})": "Connectat ({{type}})", "Connection failed": "La connexió ha fallat", + "Connection lost. Reconnecting...": "", "Connection successful": "Connexió correcta", "Connection Type": "Tipus de connexió", "Connections": "Connexions", @@ -526,6 +534,8 @@ "Delete All Chats": "Eliminar tots els xats", "Delete all contents inside this folder": "Eliminar tot el contingut d'aquesta carpeta", "Delete automation?": "Eliminar l'automatització", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Eliminar xat", "Delete chat?": "Eliminar el xat?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "No s'ha pogut connecta al servidor de terminal {{URL}}", "Failed to copy link": "No s'ha pogut copiar l'enllaç", "Failed to create API Key.": "No s'ha pogut crear la clau API.", + "Failed to delete calendar": "", "Failed to delete note": "No s'ha pogut eliminar la nota", "Failed to download image": "No s'ha pogut descarregar la imatge", "Failed to extract content from the file: {{error}}": "No s'ha pogut extreure el contingut del fitxer: {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Esforç de raonament", "Reasoning Tags": "Etiqueta de raonament", "Recently Used": "Recentment utilitzat", + "Reconnected": "", "Record": "Enregistrar", "Record voice": "Enregistrar la veu", "Redirecting you to Open WebUI Community": "Redirigint-te a la comunitat OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Rellevància", "Relevance Threshold": "Límit de rellevància", "Remember Dismissal": "Recordar la decisió de refutar", + "Reminder": "", "Remove": "Eliminar", "Remove {{MODELID}} from list.": "Eliminar {{MODELID}} de la llista", "Remove action": "Eliminar l'acció", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Iniciar una nova conversa", "Start of the channel": "Inici del canal", "Start Tag": "Etiqueta d'inici", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Iniciant el kernel...", + "Starting now": "", "State": "Estat", "Status": "Estat", "Status cleared successfully": "S'ha eliminat correctament el teu estat", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Això eliminarà {{NAME}} i tots els continguts.", "This will delete all models including custom models": "Això eliminarà tots els models incloent els personalitzats", "This will delete all models including custom models and cannot be undone.": "Això eliminarà tots els models incloent els personalitzats i no es pot desfer", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Això restablirà la base de coneixement i sincronitzarà tots els fitxers. Vols continuar?", "Thorough explanation": "Explicació en detall", "Thought": "Pensament", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Es descarrega {{FROM_NOW}}", "Unlock mysteries": "Desbloqueja els misteris", "Unpin": "Alliberar", + "Unpin from Sidebar": "", "Unravel secrets": "Descobreix els secrets", "Unshare Chat": "Deixar de compartir el xat", "Unsupported file type.": "Tipus no suportat", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index db49608fee..d1278ac30b 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "", "{{webUIName}} Backend Required": "Backend {{webUIName}} gikinahanglan", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Mga koneksyon", @@ -525,6 +533,8 @@ "Delete All Chats": "", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Irekord ang tingog", "Redirecting you to Open WebUI Community": "Gi-redirect ka sa komunidad sa OpenWebUI", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Sinugdan sa channel", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/cs-CZ/translation.json b/src/lib/i18n/locales/cs-CZ/translation.json index b2ec0ebb05..a787579837 100644 --- a/src/lib/i18n/locales/cs-CZ/translation.json +++ b/src/lib/i18n/locales/cs-CZ/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "Konverzace uživatele {{user}}", "{{webUIName}} Backend Required": "Je vyžadován backend {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*Pro generování obrázků jsou vyžadována ID uzlů instrukce", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Nová verze (v{{LATEST_VERSION}}) je nyní k dispozici.", @@ -204,6 +209,7 @@ "Ask a question": "Položit otázku", "Assistant": "Asistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Připojit znalosti", @@ -278,6 +284,7 @@ "Bypass Web Loader": "Obejít webový zavaděč", "Cache Base Model List": "Ukládat seznam základních modelů do mezipaměti", "Calendar": "Kalendář", + "Calendar deleted": "", "Calendars": "", "Call": "Volání", "Call feature is not supported when using Web STT engine": "Funkce volání není podporována při použití webového STT jádra.", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Připojte se k vlastním externím serverům nástrojů kompatibilním s OpenAPI.", "Connected ({{type}})": "", "Connection failed": "Připojení se nezdařilo", + "Connection lost. Reconnecting...": "", "Connection successful": "Připojení úspěšné", "Connection Type": "Typ připojení", "Connections": "Připojení", @@ -527,6 +535,8 @@ "Delete All Chats": "Smazat všechny konverzace", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Smazat konverzaci", "Delete chat?": "Smazat konverzaci?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Nepodařilo se zkopírovat odkaz", "Failed to create API Key.": "Nepodařilo se vytvořit API klíč.", + "Failed to delete calendar": "", "Failed to delete note": "Nepodařilo se smazat poznámku", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "Nepodařilo se extrahovat obsah ze souboru: {{error}}", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "reasoning effort", "Reasoning Tags": "reasoning tags", "Recently Used": "", + "Reconnected": "", "Record": "Nahrát", "Record voice": "Nahrát hlas", "Redirecting you to Open WebUI Community": "Přesměrovávám vás do komunity Open WebUI", @@ -1650,6 +1662,7 @@ "Relevance": "Relevance", "Relevance Threshold": "Prahová hodnota relevance", "Remember Dismissal": "Pamatovat si zavření", + "Reminder": "", "Remove": "Odebrat", "Remove {{MODELID}} from list.": "Odebrat {{MODELID}} ze seznamu.", "Remove action": "", @@ -1898,7 +1911,13 @@ "Start a new conversation": "", "Start of the channel": "Začátek kanálu", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "Tím se smaže {{NAME}} a veškerý jeho obsah.", "This will delete all models including custom models": "Tím se smažou všechny modely včetně vlastních modelů", "This will delete all models including custom models and cannot be undone.": "Tím se smažou všechny modely včetně vlastních a tuto akci nelze vrátit zpět.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Tím se resetuje znalostní báze a synchronizují se všechny soubory. Přejete si pokračovat?", "Thorough explanation": "Důkladné vysvětlení", "Thought": "", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "Uvolní se {{FROM_NOW}}", "Unlock mysteries": "Odhalte záhady", "Unpin": "Odepnout", + "Unpin from Sidebar": "", "Unravel secrets": "Rozplétejte tajemství", "Unshare Chat": "", "Unsupported file type.": "Nepodporovaný typ souboru.", diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json index 09d336d179..cde38de62f 100644 --- a/src/lib/i18n/locales/da-DK/translation.json +++ b/src/lib/i18n/locales/da-DK/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}s chats", "{{webUIName}} Backend Required": "{{webUIName}} Backend kræves", "*Prompt node ID(s) are required for image generation": "*Prompt node ID(s) er påkrævet for at kunne generere billeder", + "1 hour before": "", "1 Source": "1 kilde", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "En samarbejdskanal hvor folk tilmelder sig som medlemmer", "A discussion channel where access is controlled by groups and permissions": "En diskussionskanal hvor adgang styres af grupper og tilladelser", "A new version (v{{LATEST_VERSION}}) is now available.": "En ny version (v{{LATEST_VERSION}}) er nu tilgængelig.", @@ -202,6 +207,7 @@ "Ask a question": "Stil et spørgsmål", "Assistant": "Assistent", "Async Embedding Processing": "Asynkron embedding processering", + "At time of event": "", "Attach File From Knowledge": "Vedhæft fil fra viden", "Attach Files": "", "Attach Knowledge": "Vedhæft viden", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Omgå Web Loader", "Cache Base Model List": "Cache Base Model List", "Calendar": "Kalender", + "Calendar deleted": "", "Calendars": "", "Call": "Opkald", "Call feature is not supported when using Web STT engine": "Opkaldsfunktion er ikke understøttet for Web STT engine", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Opret forbindelse til dine egne OpenAPI kompatible eksterne værktøjsservere.", "Connected ({{type}})": "", "Connection failed": "Forbindelse mislykkedes", + "Connection lost. Reconnecting...": "", "Connection successful": "Forbindelse lykkedes", "Connection Type": "Forbindelsestype", "Connections": "Forbindelser", @@ -525,6 +533,8 @@ "Delete All Chats": "Slet alle chats", "Delete all contents inside this folder": "Slet alt indhold i denne mappe", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Slet chat", "Delete chat?": "Slet chat?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Kunne ikke kopiere link", "Failed to create API Key.": "Kunne ikke oprette API-nøgle.", + "Failed to delete calendar": "", "Failed to delete note": "Kunne ikke slette note", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "Kunne ikke udtrække indhold fra filen: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Ræsonnements indsats", "Reasoning Tags": "Ræsonneringstags", "Recently Used": "", + "Reconnected": "", "Record": "Optag", "Record voice": "Optag stemme", "Redirecting you to Open WebUI Community": "Omdirigerer dig til OpenWebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "Relevans", "Relevance Threshold": "Relevans tærskel", "Remember Dismissal": "Husk afvisning", + "Reminder": "", "Remove": "Fjern", "Remove {{MODELID}} from list.": "Fjern {{MODELID}} fra listen.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Start en ny samtale", "Start of the channel": "Kanalens start", "Start Tag": "Start tag", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "Status", "Status cleared successfully": "Status slettet", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Dette vil slette {{NAME}} og alt dens indhold.", "This will delete all models including custom models": "Dette vil slette alle modeller, inklusive brugerdefinerede modeller", "This will delete all models including custom models and cannot be undone.": "Dette vil slette alle modeller, inklusive brugerdefinerede modeller og kan ikke fortrydes.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Dette vil nulstille vidensbasen og synkronisere alle filer. Vil du fortsætte?", "Thorough explanation": "Grundig forklaring", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Aflaster {{FROM_NOW}}", "Unlock mysteries": "Lås op for mysterier", "Unpin": "Frigør", + "Unpin from Sidebar": "", "Unravel secrets": "Afslør hemmeligheder", "Unshare Chat": "", "Unsupported file type.": "Ikke-understøttet filtype.", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 5cd8fc30e6..aec1910274 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "Chats von {{user}}", "{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich", "*Prompt node ID(s) are required for image generation": "*Prompt-Node-ID(s) sind für die Bildgenerierung erforderlich", + "1 hour before": "", "1 Source": "1 Quelle", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "vor 1 Minute", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Ein Kanal zur Zusammenarbeit, dem Mitglieder beitreten können", "A discussion channel where access is controlled by groups and permissions": "Ein Diskussionskanal, dessen Zugriff durch Gruppen und Berechtigungen gesteuert wird", "A new version (v{{LATEST_VERSION}}) is now available.": "Eine neue Version (v{{LATEST_VERSION}}) ist jetzt verfügbar.", @@ -202,6 +207,7 @@ "Ask a question": "Stellen Sie eine Frage", "Assistant": "Assistent", "Async Embedding Processing": "Asynchrone Embedding-Verarbeitung", + "At time of event": "", "Attach File From Knowledge": "Datei aus Wissensspeicher anhängen", "Attach Files": "Dateien anhängen", "Attach Knowledge": "Wissen anhängen", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Web-Loader umgehen", "Cache Base Model List": "Basismodell-Liste cachen", "Calendar": "Kalender", + "Calendar deleted": "", "Calendars": "", "Call": "Anruf", "Call feature is not supported when using Web STT engine": "Die Anruffunktion wird bei Verwendung der Web-STT-Engine nicht unterstützt.", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Verbinden Sie Ihre eigenen OpenAPI-kompatiblen externen Tool-Server.", "Connected ({{type}})": "Verbunden ({{type}})", "Connection failed": "Verbindung fehlgeschlagen", + "Connection lost. Reconnecting...": "", "Connection successful": "Verbindung erfolgreich", "Connection Type": "Verbindungstyp", "Connections": "Verbindungen", @@ -525,6 +533,8 @@ "Delete All Chats": "Alle Chats löschen", "Delete all contents inside this folder": "Alle Inhalte in diesem Ordner löschen", "Delete automation?": "Automatisierung löschen?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Chat löschen", "Delete chat?": "Chat löschen?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "Fehler beim Verbinden zum Terminal Server {{URL}}", "Failed to copy link": "Link konnte nicht kopiert werden", "Failed to create API Key.": "API-Schlüssel konnte nicht erstellt werden.", + "Failed to delete calendar": "", "Failed to delete note": "Notiz konnte nicht gelöscht werden", "Failed to download image": "Bild konnte nicht heruntergeladen werden", "Failed to extract content from the file: {{error}}": "Inhaltsextraktion fehlgeschlagen: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Reasoning Effort", "Reasoning Tags": "Reasoning Tags", "Recently Used": "Kürzlich verwendet", + "Reconnected": "", "Record": "Aufnehmen", "Record voice": "Stimme aufnehmen", "Redirecting you to Open WebUI Community": "Sie werden zur Open WebUI Community weitergeleitet", @@ -1648,6 +1660,7 @@ "Relevance": "Relevanz", "Relevance Threshold": "Relevanzschwelle", "Remember Dismissal": "Ausblendung merken", + "Reminder": "", "Remove": "Entfernen", "Remove {{MODELID}} from list.": "{{MODELID}} von der Liste entfernen.", "Remove action": "Action entfernen", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Neue Unterhaltung beginnen", "Start of the channel": "Beginn des Kanals", "Start Tag": "Start-Tag", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Kernel starten...", + "Starting now": "", "State": "Zustand", "Status": "Status", "Status cleared successfully": "Status erfolgreich gelöscht", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Dies löscht {{NAME}} und alle Inhalte.", "This will delete all models including custom models": "Dies löscht alle Modelle, einschließlich benutzerdefinierter Modelle", "This will delete all models including custom models and cannot be undone.": "Dies löscht alle Modelle, einschließlich benutzerdefinierter Modelle, und kann nicht rückgängig gemacht werden.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Dadurch wird der Wissensspeicher zurückgesetzt und alle Dateien werden synchronisiert. Möchten Sie fortfahren?", "Thorough explanation": "Ausführliche Erklärung", "Thought": "Gedanke", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Entlädt {{FROM_NOW}}", "Unlock mysteries": "Geheimnisse entschlüsseln", "Unpin": "Lösen", + "Unpin from Sidebar": "", "Unravel secrets": "Geheimnisse lüften", "Unshare Chat": "Chat-Freigabe entfernen", "Unsupported file type.": "Nicht unterstützter Dateityp.", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index f1e6fddc73..b4a402abac 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "", "{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Connections", @@ -525,6 +533,8 @@ "Delete All Chats": "", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Record Bark", "Redirecting you to Open WebUI Community": "Redirecting you to Open WebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Start of channel", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/el-GR/translation.json b/src/lib/i18n/locales/el-GR/translation.json index 3d59704cdb..22391542aa 100644 --- a/src/lib/i18n/locales/el-GR/translation.json +++ b/src/lib/i18n/locales/el-GR/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "Συνομιλίες του {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Απαιτείται Backend", "*Prompt node ID(s) are required for image generation": "*Τα αναγνωριστικά κόμβου Prompt απαιτούνται για τη δημιουργία εικόνων", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Μια νέα έκδοση (v{{LATEST_VERSION}}) είναι τώρα διαθέσιμη.", @@ -202,6 +207,7 @@ "Ask a question": "Ρωτήστε μια ερώτηση", "Assistant": "Βοηθός", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Προσθήκη Knowledge", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Παράκαμψη Φορτωτή Διαδικτύου", "Cache Base Model List": "Αποθήκευση Λίστας Βασικών Μοντέλων Στην Κρυφή Μνήμη", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Κλήση", "Call feature is not supported when using Web STT engine": "Η λειτουργία κλήσης δεν υποστηρίζεται όταν χρησιμοποιείται η μηχανή Web STT", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Συνδεθείτε στους δικούς σας διακομιστές εξωτερικών εργαλείων συμβατών με OpenAPI.", "Connected ({{type}})": "", "Connection failed": "Σύνδεση απέτυχε", + "Connection lost. Reconnecting...": "", "Connection successful": "Σύνδεση επιτυχής", "Connection Type": "Είδος Σύνδεσης", "Connections": "Συνδέσεις", @@ -525,6 +533,8 @@ "Delete All Chats": "Διαγραφή Όλων των Συνομιλιών", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Διαγραφή Συνομιλίας", "Delete chat?": "Διαγραφή συνομιλίας;", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Αποτυχία αντιγραφής συνδέσμου", "Failed to create API Key.": "Αποτυχία δημιουργίας Κλειδιού API.", + "Failed to delete calendar": "", "Failed to delete note": "Αποτυχία διαγραφής σημειώσεως", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Εγγραφή φωνής", "Redirecting you to Open WebUI Community": "Μετακατεύθυνση στην Κοινότητα OpenWebUI", @@ -1648,6 +1660,7 @@ "Relevance": "Σχετικότητα", "Relevance Threshold": "Όριο Σχετικότητας", "Remember Dismissal": "Θύμηση Απόρριψης", + "Reminder": "", "Remove": "Αφαίρεση", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Αρχή του καναλιού", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Αυτό θα διαγράψει το {{NAME}} και όλο το περιεχόμενό του.", "This will delete all models including custom models": "Αυτό θα διαγράψει όλα τα μοντέλα, συμπεριλαμβανομένων των προσαρμοσμένων μοντέλων", "This will delete all models including custom models and cannot be undone.": "Αυτό θα διαγράψει όλα τα μοντέλα, συμπεριλαμβανομένων των προσαρμοσμένων μοντέλων και δεν μπορεί να αναιρεθεί.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Αυτό θα επαναφέρει τη βάση γνώσης και θα συγχρονίσει όλα τα αρχεία. Θέλετε να συνεχίσετε;", "Thorough explanation": "Λεπτομερής εξήγηση", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Ξεκλείδωμα μυστηρίων", "Unpin": "Ξεκαρφίτσωμα", + "Unpin from Sidebar": "", "Unravel secrets": "Ξετυλίξτε μυστικά", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index d24b7aeda5..88cfb9a311 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "", "{{webUIName}} Backend Required": "", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "", @@ -525,6 +533,8 @@ "Delete All Chats": "", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "", "Redirecting you to Open WebUI Community": "", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index b53f2ae485..ad0f42f733 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "", "{{webUIName}} Backend Required": "", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "", @@ -525,6 +533,8 @@ "Delete All Chats": "", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "", "Redirecting you to Open WebUI Community": "", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index 2958a4ddfd..2f44afcee4 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Chats de {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido", "*Prompt node ID(s) are required for image generation": "Los ID de nodo son requeridos para la generación de imágenes", + "1 hour before": "", "1 Source": "1 Fuente", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "hace_1m", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Canal colaborativo donde la gente se une como miembro", "A discussion channel where access is controlled by groups and permissions": "Un canal de discusión con el acceso controlado mediante grupos y permisos", "A new version (v{{LATEST_VERSION}}) is now available.": "Nueva versión (v{{LATEST_VERSION}}) disponible.", @@ -203,6 +208,7 @@ "Ask a question": "Haz una pregunta", "Assistant": "Asistente", "Async Embedding Processing": "Procesado Asíncrono al Incrustrar", + "At time of event": "", "Attach File From Knowledge": "Adjuntar Archivo desde Conocimiento", "Attach Files": "Adjuntar Archivos", "Attach Knowledge": "Adjuntar Conocimiento", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Desactivar Cargar de Web", "Cache Base Model List": "Cachear Lista de Cache Modelos", "Calendar": "Calendario", + "Calendar deleted": "", "Calendars": "", "Call": "Llamada", "Call feature is not supported when using Web STT engine": "La funcionalidad de Llamada no está soportada cuando se usa el motor Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Conectar a tus propios endpoints externos de herramientas compatibles con OpenAPI.", "Connected ({{type}})": "Connectado ({{type}})", "Connection failed": "Conexión fallida", + "Connection lost. Reconnecting...": "", "Connection successful": "Conexión realizada", "Connection Type": "Tipo de Conexión", "Connections": "Conexiones", @@ -526,6 +534,8 @@ "Delete All Chats": "Borrar todos los chats", "Delete all contents inside this folder": "Borrar todo el contenido de esta carpeta", "Delete automation?": "¿Borrar automatización?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Borrar Chat", "Delete chat?": "¿Borrar el chat?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "Fallo al conectar al servidor de terminal: {{URL}}", "Failed to copy link": "Fallo al copiar enlace", "Failed to create API Key.": "Fallo al crear la Clave API.", + "Failed to delete calendar": "", "Failed to delete note": "Fallo al eliminar nota", "Failed to download image": "Fallo al descargar imagen", "Failed to extract content from the file: {{error}}": "Fallo al extraer el contenido del archivo: {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Esfuerzo del Razonamiento", "Reasoning Tags": "Etiquetas de Razonamiento", "Recently Used": "Usado Recientemente", + "Reconnected": "", "Record": "Grabar", "Record voice": "Grabar voz", "Redirecting you to Open WebUI Community": "Redireccionando a la Comunidad Open-WebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Relevancia", "Relevance Threshold": "Umbral de Relevancia", "Remember Dismissal": "Recordar Descartes (de notificaciones)", + "Reminder": "", "Remove": "Eliminar", "Remove {{MODELID}} from list.": "Eliminar {{MODELID}} de la lista.", "Remove action": "Eliminar acción", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Comenzar una conversación nueva", "Start of the channel": "Inicio del canal", "Start Tag": "Etiqueta de Inicio", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Iniciando el núcleo...", + "Starting now": "", "State": "Estado", "Status": "Estado", "Status cleared successfully": "Estado limpiado correctamente", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Esto eliminará {{NAME}} y todo su contenido.", "This will delete all models including custom models": "Esto eliminará todos los modelos, incluidos los modelos personalizados", "This will delete all models including custom models and cannot be undone.": "Esto eliminará todos los modelos, incluidos los modelos personalizados y no se puede deshacer.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Esto reinicializará la base de conocimientos y sincronizará todos los archivos. ¿Desea continuar?", "Thorough explanation": "Explicación exhaustiva", "Thought": "Pensando", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Descargas {{FROM_NOW}}", "Unlock mysteries": "Desbloquear misterios", "Unpin": "Desfijar", + "Unpin from Sidebar": "", "Unravel secrets": "Desentrañar secretos", "Unshare Chat": "Descompartir Chat", "Unsupported file type.": "Tipo de archivo no soportado", diff --git a/src/lib/i18n/locales/et-EE/translation.json b/src/lib/i18n/locales/et-EE/translation.json index a7da9119d8..a0ce487ea6 100644 --- a/src/lib/i18n/locales/et-EE/translation.json +++ b/src/lib/i18n/locales/et-EE/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} vestlused", "{{webUIName}} Backend Required": "{{webUIName}} taustaserver on vajalik", "*Prompt node ID(s) are required for image generation": "*Sisendi sõlme ID(d) on piltide genereerimiseks vajalikud", + "1 hour before": "", "1 Source": "1 allikas", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1m tagasi", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Koostöökanal, kuhu inimesed liituvad liikmetena", "A discussion channel where access is controlled by groups and permissions": "Arutelukanal, kus juurdepääsu kontrollivad grupid ja õigused", "A new version (v{{LATEST_VERSION}}) is now available.": "Uus versioon (v{{LATEST_VERSION}}) on saadaval.", @@ -202,6 +207,7 @@ "Ask a question": "Esita küsimus", "Assistant": "Assistent", "Async Embedding Processing": "Asünkroonne manustamise töötlemine", + "At time of event": "", "Attach File From Knowledge": "Lisa fail teadmistest", "Attach Files": "", "Attach Knowledge": "Lisa teadmised", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Jäta veebilaadija vahele", "Cache Base Model List": "Puhverda baasmudelite nimekiri", "Calendar": "Kalender", + "Calendar deleted": "", "Calendars": "", "Call": "Kõne", "Call feature is not supported when using Web STT engine": "Kõnefunktsioon ei ole Web STT mootorit kasutades toetatud", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Ühendu oma OpenAPI-ga ühilduvate väliste tööriistaserveritega.", "Connected ({{type}})": "", "Connection failed": "Ühendus ebaõnnestus", + "Connection lost. Reconnecting...": "", "Connection successful": "Ühendus õnnestus", "Connection Type": "Ühenduse tüüp", "Connections": "Ühendused", @@ -525,6 +533,8 @@ "Delete All Chats": "Kustuta kõik vestlused", "Delete all contents inside this folder": "Kustuta kogu selle kausta sisu", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Kustuta vestlus", "Delete chat?": "Kustutada vestlus?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "Ühendamine {{URL}} terminali serveriga ebaõnnestus", "Failed to copy link": "Lingi kopeerimine ebaõnnestus", "Failed to create API Key.": "API võtme loomine ebaõnnestus.", + "Failed to delete calendar": "", "Failed to delete note": "Märkme kustutamine ebaõnnestus", "Failed to download image": "Pildi allalaadimine ebaõnnestus", "Failed to extract content from the file: {{error}}": "Failist sisu eraldamine ebaõnnestus: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Arutluspingutus", "Reasoning Tags": "Arutlussildid", "Recently Used": "", + "Reconnected": "", "Record": "Salvesta", "Record voice": "Salvesta hääl", "Redirecting you to Open WebUI Community": "Suunamine Open WebUI kogukonda", @@ -1648,6 +1660,7 @@ "Relevance": "Asjakohasus", "Relevance Threshold": "Asjakohasuse lävi", "Remember Dismissal": "Pea sulgemist meeles", + "Reminder": "", "Remove": "Eemalda", "Remove {{MODELID}} from list.": "Eemalda {{MODELID}} nimekirjast.", "Remove action": "Eemalda toiming", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Alusta uut vestlust", "Start of the channel": "Kanali algus", "Start Tag": "Algussilt", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Kerneli käivitamine...", + "Starting now": "", "State": "", "Status": "Olek", "Status cleared successfully": "Olek edukalt tühjendatud", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "See kustutab {{NAME}} ja kogu selle sisu.", "This will delete all models including custom models": "See kustutab kõik mudelid, sealhulgas kohandatud mudelid", "This will delete all models including custom models and cannot be undone.": "See kustutab kõik mudelid, sealhulgas kohandatud mudelid, ja seda ei saa tagasi võtta.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "See lähtestab teadmiste baasi ja sünkroniseerib kõik failid. Kas soovite jätkata?", "Thorough explanation": "Põhjalik selgitus", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Laaditakse maha {{FROM_NOW}}", "Unlock mysteries": "Ava mõistatused", "Unpin": "Eemalda kinnitus", + "Unpin from Sidebar": "", "Unravel secrets": "Ava saladused", "Unshare Chat": "Lõpeta vestluse jagamine", "Unsupported file type.": "Toetamata failitüüp.", diff --git a/src/lib/i18n/locales/eu-ES/translation.json b/src/lib/i18n/locales/eu-ES/translation.json index b8314d577a..bbb86b2023 100644 --- a/src/lib/i18n/locales/eu-ES/translation.json +++ b/src/lib/i18n/locales/eu-ES/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}-ren Txatak", "{{webUIName}} Backend Required": "{{webUIName}} Backend-a Beharrezkoa", "*Prompt node ID(s) are required for image generation": "Prompt nodoaren IDa(k) beharrezkoak dira irudiak sortzeko", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Bertsio berri bat (v{{LATEST_VERSION}}) eskuragarri dago orain.", @@ -202,6 +207,7 @@ "Ask a question": "Egin galdera bat", "Assistant": "Laguntzailea", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Deia", "Call feature is not supported when using Web STT engine": "Dei funtzioa ez da onartzen Web STT motorra erabiltzean", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Konexioak", @@ -525,6 +533,8 @@ "Delete All Chats": "Ezabatu Txat Guztiak", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Ezabatu Txata", "Delete chat?": "Ezabatu txata?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Huts egin du API Gakoa sortzean.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Grabatu ahotsa", "Redirecting you to Open WebUI Community": "OpenWebUI Komunitatera berbideratzen", @@ -1648,6 +1660,7 @@ "Relevance": "Garrantzia", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Kendu", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Kanalaren hasiera", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Honek {{NAME}} eta bere eduki guztiak ezabatuko ditu.", "This will delete all models including custom models": "Honek modelo guztiak ezabatuko ditu, modelo pertsonalizatuak barne", "This will delete all models including custom models and cannot be undone.": "Honek modelo guztiak ezabatuko ditu, modelo pertsonalizatuak barne, eta ezin da desegin.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Honek ezagutza-basea berrezarri eta fitxategi guztiak sinkronizatuko ditu. Jarraitu nahi duzu?", "Thorough explanation": "Azalpen sakona", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Askatu misterioak", "Unpin": "Kendu aingura", + "Unpin from Sidebar": "", "Unravel secrets": "Askatu sekretuak", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index 3dd9dc4d32..39de7bb016 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} گفتگوهای", "{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.", "*Prompt node ID(s) are required for image generation": "*شناسه(های) گره پرامپت برای تولید تصویر مورد نیاز است", + "1 hour before": "", "1 Source": "۱ منبع", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "نسخه جدید (v{{LATEST_VERSION}}) در دسترس است.", @@ -202,6 +207,7 @@ "Ask a question": "سوالی بپرسید", "Assistant": "دستیار", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "پیوست فایل از دانش", "Attach Files": "", "Attach Knowledge": "پیوست دانش", @@ -276,6 +282,7 @@ "Bypass Web Loader": "دور زدن بارگذاری وب", "Cache Base Model List": "کش لیست مدل پایه", "Calendar": "تقویم", + "Calendar deleted": "", "Calendars": "", "Call": "تماس", "Call feature is not supported when using Web STT engine": "ویژگی تماس هنگام استفاده از موتور Web STT پشتیبانی نمی\u200cشود", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "به سرورهای ابزار خارجی سازگار با OpenAPI خود متصل شوید.", "Connected ({{type}})": "", "Connection failed": "اتصال ناموفق بود", + "Connection lost. Reconnecting...": "", "Connection successful": "اتصال موفقیت\u200cآمیز بود", "Connection Type": "نوع اتصال", "Connections": "ارتباطات", @@ -525,6 +533,8 @@ "Delete All Chats": "حذف همه گفتگوها", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "حذف گپ", "Delete chat?": "گفتگو حذف شود؟", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "کپی لینک ناموفق بود", "Failed to create API Key.": "ایجاد کلید API با خطا مواجه شد.", + "Failed to delete calendar": "", "Failed to delete note": "حذف یادداشت ناموفق بود", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "استخراج محتوا از فایل ناموفق بود: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "تلاش استدلال", "Reasoning Tags": "تگ\u200cهای استدلال", "Recently Used": "", + "Reconnected": "", "Record": "ضبط", "Record voice": "ضبط صدا", "Redirecting you to Open WebUI Community": "در حال هدایت به OpenWebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "ارتباط", "Relevance Threshold": "آستانه ارتباط", "Remember Dismissal": "به خاطر سپردن رد کردن", + "Reminder": "", "Remove": "حذف", "Remove {{MODELID}} from list.": "حذف {{MODELID}} از لیست.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "شروع یک مکالمه جدید", "Start of the channel": "آغاز کانال", "Start Tag": "تگ شروع", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "این {{NAME}} و تمام محتویات آن را حذف خواهد کرد.", "This will delete all models including custom models": "این همه مدل\u200cها از جمله مدل\u200cهای سفارشی را حذف خواهد کرد", "This will delete all models including custom models and cannot be undone.": "این همه مدل\u200cها از جمله مدل\u200cهای سفارشی را حذف خواهد کرد و قابل بازگشت نیست.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "این پایگاه دانش را بازنشانی کرده و همه فایل\u200cها را همگام\u200cسازی خواهد کرد. آیا می\u200cخواهید ادامه دهید؟", "Thorough explanation": "توضیح کامل", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "خارج می\u200cشود {{FROM_NOW}}", "Unlock mysteries": "رمزگشایی از اسرار", "Unpin": "برداشتن پین", + "Unpin from Sidebar": "", "Unravel secrets": "کشف رازها", "Unshare Chat": "", "Unsupported file type.": "نوع فایل پشتیبانی نمی\u200cشود.", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index 7c856082bc..16501646eb 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}:n keskustelut", "{{webUIName}} Backend Required": "{{webUIName}}-backend vaaditaan", "*Prompt node ID(s) are required for image generation": "Kuvan luomiseen vaaditaan kehote-solmun ID(t)", + "1 hour before": "", "1 Source": "1 lähde", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Yhteistyökanava, johon ihmiset liittyvät jäseninä", "A discussion channel where access is controlled by groups and permissions": "Keskustelukanava, johon pääsyä rajoitetaan ryhmillä ja käyttöoikeuksilla", "A new version (v{{LATEST_VERSION}}) is now available.": "Uusi versio (v{{LATEST_VERSION}}) on nyt saatavilla.", @@ -202,6 +207,7 @@ "Ask a question": "Kysy kysymys", "Assistant": "Avustaja", "Async Embedding Processing": "Asynkroninen upotus prosessointi", + "At time of event": "", "Attach File From Knowledge": "Liitä tiedosto tietämyksestä", "Attach Files": "", "Attach Knowledge": "Liitä tietoa", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Ohita verkkolataaja", "Cache Base Model List": "Malli luettelon välimuisti", "Calendar": "Kalenteri", + "Calendar deleted": "", "Calendars": "", "Call": "Puhelu", "Call feature is not supported when using Web STT engine": "Puhelutoimintoa ei tueta käytettäessä web-puheentunnistusmoottoria", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Yhdistä omat ulkopuoliset OpenAPI yhteensopivat työkalu palvelimet.", "Connected ({{type}})": "Yhdistetty ({{type}})", "Connection failed": "Yhteys epäonnistui", + "Connection lost. Reconnecting...": "", "Connection successful": "Yhteys onnistui", "Connection Type": "Yhteystyyppi", "Connections": "Yhteydet", @@ -525,6 +533,8 @@ "Delete All Chats": "Poista kaikki keskustelut", "Delete all contents inside this folder": "Poista kaikki sisällöt tästä kansiosta", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Poista keskustelu", "Delete chat?": "Haluatko varmasti poistaa tämän keskustelun?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "Yhdistäminen {{URL}} päätepalvelimeen epäonnistui", "Failed to copy link": "Linkin kopiointi epäonnistui", "Failed to create API Key.": "API-avaimen luonti epäonnistui.", + "Failed to delete calendar": "", "Failed to delete note": "Muistiinpanon poistaminen epäonnistui", "Failed to download image": "Kuvan lataaminen epäonnistui", "Failed to extract content from the file: {{error}}": "Tiedoston sisällön pomiminen epäonnistui: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Päättelyn määrä", "Reasoning Tags": "Päättely tagit", "Recently Used": "", + "Reconnected": "", "Record": "Nauhoita", "Record voice": "Nauhoita ääntä", "Redirecting you to Open WebUI Community": "Ohjataan sinut OpenWebUI-yhteisöön", @@ -1648,6 +1660,7 @@ "Relevance": "Relevanssi", "Relevance Threshold": "Relevanssikynnys", "Remember Dismissal": "Muista sulkeminen", + "Reminder": "", "Remove": "Poista", "Remove {{MODELID}} from list.": "Poista {{MODELID}} listalta", "Remove action": "Poista toiminto", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Aloita uusi keskustelu", "Start of the channel": "Kanavan alku", "Start Tag": "Aloitus tagi", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Käynnistetään kerneliä...", + "Starting now": "", "State": "", "Status": "Tila", "Status cleared successfully": "Tila poistettu onnistuneesti", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Tämä poistaa {{NAME}} ja kaikki sen sisällöt.", "This will delete all models including custom models": "Tämä poistaa kaikki mallit mukaan lukien mukautetut mallit", "This will delete all models including custom models and cannot be undone.": "Tämä poistaa kaikki mallit, mukaan lukien mukautetut mallit, eikä sitä voi peruuttaa.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Tämä nollaa tietokannan ja synkronoi kaikki tiedostot. Haluatko jatkaa?", "Thorough explanation": "Perusteellinen selitys", "Thought": "Ajatus", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Purkuja {{FROM_NOW}}", "Unlock mysteries": "Selvitä arvoituksia", "Unpin": "Irrota kiinnitys", + "Unpin from Sidebar": "", "Unravel secrets": "Avaa salaisuuksia", "Unshare Chat": "Lopeta keskustelun jakaminen", "Unsupported file type.": "Ei tuettu tiedostotyyppi", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index a91ad0b618..d59ea0abf3 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Conversations de {{user}}", "{{webUIName}} Backend Required": "Backend {{webUIName}} requis", "*Prompt node ID(s) are required for image generation": "*Les ID de noeud du prompt sont nécessaires pour la génération d'images", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Une nouvelle version (v{{LATEST_VERSION}}) est disponible.", @@ -203,6 +208,7 @@ "Ask a question": "Posez votre question", "Assistant": "Assistant", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Ignorer le chargeur Web", "Cache Base Model List": "Mettre en cache la liste des modèles de base", "Calendar": "Calendrier", + "Calendar deleted": "", "Calendars": "", "Call": "Appeler", "Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Connectez-vous à vos serveurs d'outils externes.", "Connected ({{type}})": "", "Connection failed": "Échec de la connexion", + "Connection lost. Reconnecting...": "", "Connection successful": "Connexion réussie", "Connection Type": "Type de connexion", "Connections": "Connexions", @@ -526,6 +534,8 @@ "Delete All Chats": "Supprimer toutes les conversations", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Supprimer la Conversation", "Delete chat?": "Supprimer la conversation ?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Échec de la copie du lien", "Failed to create API Key.": "Échec de la création de la clé API.", + "Failed to delete calendar": "", "Failed to delete note": "Échec de la délétion de la note", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Effort de raisonnement", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Enregistrement", "Record voice": "Enregistrer la voix", "Redirecting you to Open WebUI Community": "Redirection vers la communauté OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Pertinence", "Relevance Threshold": "Seuil de pertinence", "Remember Dismissal": "Se souvenir du rejet", + "Reminder": "", "Remove": "Retirer", "Remove {{MODELID}} from list.": "Retirer {{MODELID}} de la liste.", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Début du canal", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Cela supprimera {{NAME}} et tout son contenu.", "This will delete all models including custom models": "Cela supprimera tous les modèles, y compris les modèles personnalisés", "This will delete all models including custom models and cannot be undone.": "Cela supprimera tous les modèles, y compris les modèles personnalisés, et ne peut pas être annulé.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Cela réinitialisera la base de connaissances et synchronisera tous les fichiers. Souhaitez-vous continuer ?", "Thorough explanation": "Explication approfondie", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Décharge {{FROM_NOW}}", "Unlock mysteries": "Déverrouiller les mystères", "Unpin": "Désépingler", + "Unpin from Sidebar": "", "Unravel secrets": "Dévoiler les secrets", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index 0de23b8898..4572e362c8 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Conversations de {{user}}", "{{webUIName}} Backend Required": "Backend {{webUIName}} requis", "*Prompt node ID(s) are required for image generation": "*Les ID de noeud du prompt sont nécessaires pour la génération d'image", + "1 hour before": "", "1 Source": "1 Source", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1min", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Un canal collaboratif où les membres rejoignent librement", "A discussion channel where access is controlled by groups and permissions": "Un canal de discussion où l'accès est contrôlé par les groupes et les permissions", "A new version (v{{LATEST_VERSION}}) is now available.": "Une nouvelle version (v{{LATEST_VERSION}}) est disponible.", @@ -203,6 +208,7 @@ "Ask a question": "Posez votre question", "Assistant": "Assistant", "Async Embedding Processing": "Traitement asynchrone des embeddings", + "At time of event": "", "Attach File From Knowledge": "Joindre un fichier depuis les connaissances", "Attach Files": "", "Attach Knowledge": "Joindre une connaissance", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Ignorer le chargeur Web", "Cache Base Model List": "Mettre en cache la liste des modèles de base", "Calendar": "Calendrier", + "Calendar deleted": "", "Calendars": "", "Call": "Appeler", "Call feature is not supported when using Web STT engine": "La fonction d'appel n'est pas prise en charge lors de l'utilisation du moteur Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Connectez-vous à vos serveurs d'outils externes.", "Connected ({{type}})": "", "Connection failed": "Échec de la connexion", + "Connection lost. Reconnecting...": "", "Connection successful": "Connexion réussie", "Connection Type": "Type de connexion", "Connections": "Connexions", @@ -526,6 +534,8 @@ "Delete All Chats": "Supprimer toutes les conversations", "Delete all contents inside this folder": "Supprimer tout le contenu de ce dossier", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Supprimer la Conversation", "Delete chat?": "Supprimer la conversation ?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "Échec de la connexion au serveur de terminal {{URL}}", "Failed to copy link": "Échec de la copie du lien", "Failed to create API Key.": "Échec de la création de la clé API.", + "Failed to delete calendar": "", "Failed to delete note": "Échec de la délétion de la note", "Failed to download image": "Échec du téléchargement de l'image", "Failed to extract content from the file: {{error}}": "Échec de l'extraction du contenu du fichier : {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Effort de raisonnement", "Reasoning Tags": "Balises de raisonnement", "Recently Used": "", + "Reconnected": "", "Record": "Enregistrement", "Record voice": "Enregistrer la voix", "Redirecting you to Open WebUI Community": "Redirection vers la communauté OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Pertinence", "Relevance Threshold": "Seuil de pertinence", "Remember Dismissal": "Se souvenir du rejet", + "Reminder": "", "Remove": "Retirer", "Remove {{MODELID}} from list.": "Retirer {{MODELID}} de la liste.", "Remove action": "Retirer l'action", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Démarrer une nouvelle conversation", "Start of the channel": "Début du canal", "Start Tag": "Balise de départ", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Démarrage du noyau...", + "Starting now": "", "State": "", "Status": "Statut", "Status cleared successfully": "Statut effacé avec succès", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Cela supprimera {{NAME}} et tout son contenu.", "This will delete all models including custom models": "Cela supprimera tous les modèles, y compris les modèles personnalisés", "This will delete all models including custom models and cannot be undone.": "Cela supprimera tous les modèles, y compris les modèles personnalisés, et ne peut pas être annulé.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Cela réinitialisera la base de connaissances et synchronisera tous les fichiers. Souhaitez-vous continuer ?", "Thorough explanation": "Explication approfondie", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Décharge {{FROM_NOW}}", "Unlock mysteries": "Déverrouiller les mystères", "Unpin": "Désépingler", + "Unpin from Sidebar": "", "Unravel secrets": "Dévoiler les secrets", "Unshare Chat": "Annuler le partage de la conversation", "Unsupported file type.": "Type de fichier non pris en charge.", diff --git a/src/lib/i18n/locales/gl-ES/translation.json b/src/lib/i18n/locales/gl-ES/translation.json index 3c36e045e9..df434bfa1a 100644 --- a/src/lib/i18n/locales/gl-ES/translation.json +++ b/src/lib/i18n/locales/gl-ES/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "Chats do {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido", "*Prompt node ID(s) are required for image generation": "Os ID do nodo son requeridos para a xeneración de imáxes", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Unha nova versión (v{{LATEST_VERSION}}) está disponible.", @@ -202,6 +207,7 @@ "Ask a question": "Fai unha pregunta", "Assistant": "Asistente", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Chamada", "Call feature is not supported when using Web STT engine": "A funcionalidade da chamada non pode usarse xunto co motor da STT Web", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Conexions", @@ -525,6 +533,8 @@ "Delete All Chats": "Eliminar todos os chats", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Borrar Chat", "Delete chat?": "Borrar o chat?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Non pudo xerarse a chave API.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Esfuerzo de razonamiento", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Grabar voz", "Redirecting you to Open WebUI Community": "Redireccionándote a a comunidad OpenWebUI", @@ -1648,6 +1660,7 @@ "Relevance": "Relevancia", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Eliminar", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Inicio da canle", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Esto eliminará {{NAME}} y todo su contido.", "This will delete all models including custom models": "Esto eliminará todos os modelos, incluidos os modelos personalizados", "This will delete all models including custom models and cannot be undone.": "Esto eliminará todos os modelos, incluidos os modelos personalizados y no se puede deshacer.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Esto reseteará la base de coñecementos y sincronizará todos os arquivos. ¿Desea continuar?", "Thorough explanation": "Explicación exhaustiva", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Desbloquear misterios", "Unpin": "Desanclar", + "Unpin from Sidebar": "", "Unravel secrets": "Desentrañar secretos", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index 2bb98c4d4e..f36e6d332e 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "צ'אטים של {{user}}", "{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -203,6 +208,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "לוח שנה", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "החיבור נכשל", + "Connection lost. Reconnecting...": "", "Connection successful": "החיבור הצליח", "Connection Type": "סוג חיבור", "Connections": "חיבורים", @@ -526,6 +534,8 @@ "Delete All Chats": "מחק את כל הצ'אטים", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "מחק צ'אט", "Delete chat?": "", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "יצירת מפתח API נכשלה.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "הקלט קול", "Redirecting you to Open WebUI Community": "מפנה אותך לקהילת OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "הסר", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "תחילת הערוץ", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_two": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "תיאור מפורט", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index ce96aa2286..eeeff64210 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} की चैट", "{{webUIName}} Backend Required": "{{webUIName}} बैकएंड आवश्यक", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "सम्बन्ध", @@ -525,6 +533,8 @@ "Delete All Chats": "सभी चैट हटाएं", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "चैट हटाएं", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "एपीआई कुंजी बनाने में विफल.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "आवाज रिकॉर्ड करना", "Redirecting you to Open WebUI Community": "आपको OpenWebUI समुदाय पर पुनर्निर्देशित किया जा रहा है", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "हटा दें", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "चैनल की शुरुआत", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "विस्तृत व्याख्या", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index 01e9f0fdf1..c0525013e9 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Razgovori korisnika {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -203,6 +208,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Poziv", "Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Povezivanja", @@ -526,6 +534,8 @@ "Delete All Chats": "Izbriši sve razgovore", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Izbriši razgovor", "Delete chat?": "", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Neuspješno stvaranje API ključa.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Snimanje glasa", "Redirecting you to Open WebUI Community": "Preusmjeravanje na OpenWebUI zajednicu", @@ -1649,6 +1661,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Ukloni", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Početak kanala", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "Detaljno objašnjenje", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/hu-HU/translation.json b/src/lib/i18n/locales/hu-HU/translation.json index 5d2fee4e33..22f4ea62cf 100644 --- a/src/lib/i18n/locales/hu-HU/translation.json +++ b/src/lib/i18n/locales/hu-HU/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} beszélgetései", "{{webUIName}} Backend Required": "{{webUIName}} Backend szükséges", "*Prompt node ID(s) are required for image generation": "*Prompt node ID(k) szükségesek a képgeneráláshoz", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Új verzió (v{{LATEST_VERSION}}) érhető el.", @@ -202,6 +207,7 @@ "Ask a question": "Kérdezz valamit", "Assistant": "Asszisztens", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Naptár", + "Calendar deleted": "", "Calendars": "", "Call": "Hívás", "Call feature is not supported when using Web STT engine": "A hívás funkció nem támogatott Web STT motor használatakor", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Csatlakozz saját OpenAPI kompatibilis külső eszköszervereidhez.", "Connected ({{type}})": "", "Connection failed": "Kapcsolat sikertelen", + "Connection lost. Reconnecting...": "", "Connection successful": "Kapcsolat sikeres", "Connection Type": "", "Connections": "Kapcsolatok", @@ -525,6 +533,8 @@ "Delete All Chats": "Minden beszélgetés törlése", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Beszélgetés törlése", "Delete chat?": "Törli a beszélgetést?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Nem sikerült létrehozni az API kulcsot.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Érvelési erőfeszítés", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Hang rögzítése", "Redirecting you to Open WebUI Community": "Átirányítás az OpenWebUI közösséghez", @@ -1648,6 +1660,7 @@ "Relevance": "Relevancia", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Eltávolítás", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "A csatorna eleje", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Ez törölni fogja a {{NAME}}-t és minden tartalmát.", "This will delete all models including custom models": "Ez törölni fogja az összes modellt, beleértve az egyéni modelleket is", "This will delete all models including custom models and cannot be undone.": "Ez törölni fogja az összes modellt, beleértve az egyéni modelleket is, és nem vonható vissza.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Ez visszaállítja a tudásbázist és szinkronizálja az összes fájlt. Szeretné folytatni?", "Thorough explanation": "Alapos magyarázat", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Titkok feloldása", "Unpin": "Rögzítés feloldása", + "Unpin from Sidebar": "", "Unravel secrets": "Titkok megfejtése", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index 2e60de3ed1..c537fb24bb 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "Obrolan {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Diperlukan Backend", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -201,6 +206,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -275,6 +281,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Panggilan", "Call feature is not supported when using Web STT engine": "Fitur panggilan tidak didukung saat menggunakan mesin Web STT", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Koneksi", @@ -524,6 +532,8 @@ "Delete All Chats": "Menghapus Semua Obrolan", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Menghapus Obrolan", "Delete chat?": "Menghapus obrolan?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Gagal membuat API Key.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Rekam suara", "Redirecting you to Open WebUI Community": "Mengarahkan Anda ke Komunitas OpenWebUI", @@ -1647,6 +1659,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Hapus", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "", "Start of the channel": "Awal saluran", "Start Tag": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "Penjelasan menyeluruh", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json index df9257c7e9..e5550ac069 100644 --- a/src/lib/i18n/locales/ie-GA/translation.json +++ b/src/lib/i18n/locales/ie-GA/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "Comhráite {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Ceoldeireadh Riachtanach", "*Prompt node ID(s) are required for image generation": "* Tá ID(anna) nód treorach ag teastáil chun íomhá a ghiniúint", + "1 hour before": "", "1 Source": "1 Foinse", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1 nóiméad ó shin", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Cainéal comhoibrithe ina mbíonn daoine ag glacadh páirte mar bhaill", "A discussion channel where access is controlled by groups and permissions": "Cainéal plé ina bhfuil rochtain rialaithe ag grúpaí agus ceadanna", "A new version (v{{LATEST_VERSION}}) is now available.": "Tá leagan nua (v {{LATEST_VERSION}}) ar fáil anois.", @@ -202,6 +207,7 @@ "Ask a question": "Cuir ceist", "Assistant": "Cúntóir", "Async Embedding Processing": "Próiseáil Leabaithe Asyncrónach", + "At time of event": "", "Attach File From Knowledge": "Ceangail Comhad ó Eolas", "Attach Files": "Ceangail Comhaid", "Attach Knowledge": "Ceangail Eolas", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Seachbhóthar Luchtaire Gréasáin", "Cache Base Model List": "Liosta Samhail Bunáite Taisce", "Calendar": "Féilire", + "Calendar deleted": "", "Calendars": "", "Call": "Glaoigh", "Call feature is not supported when using Web STT engine": "Ní thacaítear le gné glaonna agus inneall Web STT á úsáid", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Ceangail le do fhreastalaithe uirlisí seachtracha atá comhoiriúnach le OpenAPI.", "Connected ({{type}})": "Ceangailte ({{type}})", "Connection failed": "Theip ar an gceangal", + "Connection lost. Reconnecting...": "", "Connection successful": "Ceangal rathúil", "Connection Type": "Cineál Ceangail", "Connections": "Naisc", @@ -525,6 +533,8 @@ "Delete All Chats": "Scrios Gach Comhrá", "Delete all contents inside this folder": "Scrios an t-ábhar go léir atá sa fhillteán seo", "Delete automation?": "Scrios an t-uathoibriú?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Scrios Comhrá", "Delete chat?": "Scrios comhrá?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "Theip ar cheangal le freastalaí críochfoirt {{URL}}", "Failed to copy link": "Theip ar an nasc a chóipeáil", "Failed to create API Key.": "Theip ar an eochair API a chruthú.", + "Failed to delete calendar": "", "Failed to delete note": "Theip ar an nóta a scriosadh", "Failed to download image": "Theip ar an íomhá a íoslódáil", "Failed to extract content from the file: {{error}}": "Theip ar an ábhar a bhaint as an gcomhad: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Iarracht Réasúnúcháin", "Reasoning Tags": "Clibeanna Réasúnaíochta", "Recently Used": "Úsáidte le Déanaí", + "Reconnected": "", "Record": "Taifead", "Record voice": "Taifead guth", "Redirecting you to Open WebUI Community": "Tú a atreorú chuig OpenWebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "Ábharthacht", "Relevance Threshold": "Tairseach Ábharthaíochta", "Remember Dismissal": "Cuimhnigh ar an Dífhostú", + "Reminder": "", "Remove": "Bain", "Remove {{MODELID}} from list.": "Bain {{MODELID}} den liosta.", "Remove action": "Bain gníomh", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Tosaigh comhrá nua", "Start of the channel": "Tús an chainéil", "Start Tag": "Clib Tosaigh", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Ag tosú an eithne...", + "Starting now": "", "State": "Stát", "Status": "Stádas", "Status cleared successfully": "Glanadh an stádais go rathúil", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Scriosfaidh sé seo {{NAME}} agus a bhfuil ann go léir.", "This will delete all models including custom models": "Scriosfaidh sé seo gach samhail lena n-áirítear samhlacha saincheaptha", "This will delete all models including custom models and cannot be undone.": "Scriosfaidh sé seo gach samhail, lena n-áirítear samhlacha saincheaptha, agus ní féidir é a chealú.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Déanfaidh sé seo an bonn eolais a athshocrú agus gach comhad a shioncronú. Ar mhaith leat leanúint ar aghaidh?", "Thorough explanation": "Míniú críochnúil", "Thought": "Smaoineamh", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Díluchtuithe {{FROM_NOW}}", "Unlock mysteries": "Díghlasáil rúndiamhra", "Unpin": "Díphoráil", + "Unpin from Sidebar": "", "Unravel secrets": "Rúin a réiteach", "Unshare Chat": "Díroinn Comhrá", "Unsupported file type.": "Cineál comhaid nach dtacaítear leis.", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index e8b2b0f217..c94b0f7f5a 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "{{user}} Chat", "{{webUIName}} Backend Required": "{{webUIName}} Richiesta Backend", "*Prompt node ID(s) are required for image generation": "*ID nodo prompt sono necessari per la generazione di immagini", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Una nuova versione (v{{LATEST_VERSION}}) è ora disponibile.", @@ -203,6 +208,7 @@ "Ask a question": "Fai una domanda", "Assistant": "Assistente", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Bypassa il Web Loader", "Cache Base Model List": "", "Calendar": "Calendario", + "Calendar deleted": "", "Calendars": "", "Call": "Chiamata", "Call feature is not supported when using Web STT engine": "La funzione di chiamata non è supportata quando si utilizza il motore Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Connettiti ai tuoi server di tool esterni compatibili con OpenAPI.", "Connected ({{type}})": "", "Connection failed": "Connessione fallita", + "Connection lost. Reconnecting...": "", "Connection successful": "Connessione riuscita", "Connection Type": "Tipo Connessione", "Connections": "Connessioni", @@ -526,6 +534,8 @@ "Delete All Chats": "Elimina tutte le chat", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Elimina chat", "Delete chat?": "Elimina chat?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Impossibile copiare il link", "Failed to create API Key.": "Impossibile creare Chiave API.", + "Failed to delete calendar": "", "Failed to delete note": "Impossibile eliminare la nota", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Sforzo di ragionamento", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Registra", "Record voice": "Registra voce", "Redirecting you to Open WebUI Community": "Reindirizzamento alla comunità OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Rilevanza", "Relevance Threshold": "Soglia di Rilevanza", "Remember Dismissal": "", + "Reminder": "", "Remove": "Rimuovi", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Inizio del canale", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Questa opzione eliminerà {{NAME}} e tutti i suoi contenuti.", "This will delete all models including custom models": "Questa opzione eliminerà tutti i modelli, compresi i modelli personalizzati", "This will delete all models including custom models and cannot be undone.": "Questa opzione eliminerà tutti i modelli, compresi i modelli personalizzati e non può essere annullata.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Questa opzione ripristinerà la base di conoscenza e sincronizzerà tutti i file. Vuoi continuare?", "Thorough explanation": "Spiegazione dettagliata", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Scarica {{FROM_NOW}}", "Unlock mysteries": "Sblocca misteri", "Unpin": "Rimuovi fissato", + "Unpin from Sidebar": "", "Unravel secrets": "Svela segreti", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index 77aa239d44..af0c4211b5 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "{{user}} のチャット", "{{webUIName}} Backend Required": "{{webUIName}} バックエンドが必要です", "*Prompt node ID(s) are required for image generation": "*画像生成にはプロンプトノードIDが必要です", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "新しいバージョン (v{{LATEST_VERSION}}) が利用可能です。", @@ -201,6 +206,7 @@ "Ask a question": "質問する", "Assistant": "アシスタント", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "ナレッジからファイルを添付", "Attach Files": "ファイルを追加", "Attach Knowledge": "ナレッジを追加", @@ -275,6 +281,7 @@ "Bypass Web Loader": "Webローダーをバイパス", "Cache Base Model List": "ベースモデルリストをキャッシュ", "Calendar": "カレンダー", + "Calendar deleted": "", "Calendars": "", "Call": "コール", "Call feature is not supported when using Web STT engine": "Web STTエンジンを使用している場合、コール機能は使用できません", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "独自のOpenAPI互換外部ツールサーバーに接続します。", "Connected ({{type}})": "", "Connection failed": "接続に失敗しました", + "Connection lost. Reconnecting...": "", "Connection successful": "接続に成功しました", "Connection Type": "接続タイプ", "Connections": "接続", @@ -524,6 +532,8 @@ "Delete All Chats": "すべてのチャットを削除", "Delete all contents inside this folder": "", "Delete automation?": "オートメーションを削除しますか?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "チャットを削除", "Delete chat?": "チャットを削除しますか?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "リンクのコピーに失敗しました。", "Failed to create API Key.": "APIキーの作成に失敗しました。", + "Failed to delete calendar": "", "Failed to delete note": "ノートの削除に失敗しました。", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "ファイルから中身の取得に失敗しました: {{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "推理の努力", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "録音", "Record voice": "音声を録音", "Redirecting you to Open WebUI Community": "OpenWebUI コミュニティにリダイレクトしています", @@ -1647,6 +1659,7 @@ "Relevance": "関連性", "Relevance Threshold": "関連性の閾値", "Remember Dismissal": "閉じたことを記憶する", + "Reminder": "", "Remove": "削除", "Remove {{MODELID}} from list.": "{{MODELID}} をリストから削除する", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "新しい会話を開始", "Start of the channel": "チャンネルの開始", "Start Tag": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "状態", "Status": "ステータス", "Status cleared successfully": "正常にステータスをクリアしました", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "これは{{NAME}}とそのすべての内容を削除します。", "This will delete all models including custom models": "これはカスタムモデルを含むすべてのモデルを削除します", "This will delete all models including custom models and cannot be undone.": "これはカスタムモデルを含むすべてのモデルを削除し、元に戻すことはできません。", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "これは知識ベースをリセットし、すべてのファイルを同期します。続けますか?", "Thorough explanation": "詳細な説明", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}}にアンロード", "Unlock mysteries": "ミステリーを解き明かす", "Unpin": "ピン留め解除", + "Unpin from Sidebar": "", "Unravel secrets": "秘密を解き明かす", "Unshare Chat": "", "Unsupported file type.": "未対応のファイルタイプです", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index b2726bf790..acdc14db3c 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}-ის ჩათები", "{{webUIName}} Backend Required": "{{webUIName}} საჭიროა უკანაბოლო", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "1 წყარო", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "ხელმისაწვდომია ახალი ვერსია (v{{LATEST_VERSION}}).", @@ -202,6 +207,7 @@ "Ask a question": "კითხვის დასმა", "Assistant": "დამხმარე", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "ცოდნის მიმაგრება", @@ -276,6 +282,7 @@ "Bypass Web Loader": "ვებჩამტვირთავის გამოტოვება", "Cache Base Model List": "საბაზისო მოდელების სიის დაკეშვა", "Calendar": "კალენდარი", + "Calendar deleted": "", "Calendars": "", "Call": "ზარი", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "დაკავშირება ვერ მოხერხდა", + "Connection lost. Reconnecting...": "", "Connection successful": "შეერთება წარმატებულია", "Connection Type": "შეერთების ტიპი", "Connections": "კავშირები", @@ -525,6 +533,8 @@ "Delete All Chats": "ყველა ჩატის წაშლა", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "საუბრის წაშლა", "Delete chat?": "წავშალო ჩატი?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "ბმულის კოპირება ჩავარდა", "Failed to create API Key.": "API-ის გასაღების შექმნა ჩავარდა.", + "Failed to delete calendar": "", "Failed to delete note": "შენიშვნის წაშლა ჩავარდა", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "ჩაწერა", "Record voice": "ხმის ჩაწერა", "Redirecting you to Open WebUI Community": "მიმდინარეობს გადამისამართება OpenWebUI-ის საზოგადოების საიტზე", @@ -1648,6 +1660,7 @@ "Relevance": "შესაბამისობა", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "წაშლა", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "არხის დასაწყისი", "Start Tag": "დაწყების ჭდე", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "საფუძვლიანი ახსნა", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "გამოტვირთვა {{FROM_NOW}}", "Unlock mysteries": "", "Unpin": "ჩამოხსნა", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/kab-DZ/translation.json b/src/lib/i18n/locales/kab-DZ/translation.json index 4d0c32b8d6..0fc3787813 100644 --- a/src/lib/i18n/locales/kab-DZ/translation.json +++ b/src/lib/i18n/locales/kab-DZ/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "Asqerdec n {{user}}", "{{webUIName}} Backend Required": "", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "1 n weɣbalu", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Lqem amaynut n (v{{LATEST_VERSION}}), yella akka tura.", @@ -202,6 +207,7 @@ "Ask a question": "Efk-d asteqsi", "Assistant": "Amallal", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Qqen-as tamessunt", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Zgel asalay Web", "Cache Base Model List": "Ffer tabdart n tmudmiwin n taffa", "Calendar": "Awitay", + "Calendar deleted": "", "Calendars": "", "Call": "Siwel", "Call feature is not supported when using Web STT engine": "Tamahilt n usiwel ur tettwasefrak ara mi ara tesqedceḍ amsedday Web STT", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Qqen ɣer yiqeddacen-ik n yifecka imeṛṛa yeldin.", "Connected ({{type}})": "", "Connection failed": "Tuqqna d-tawezɣit", + "Connection lost. Reconnecting...": "", "Connection successful": "Tuqqna tedda akken iwata", "Connection Type": "Anaw n tuqqna", "Connections": "Tuqqniwin", @@ -525,6 +533,8 @@ "Delete All Chats": "Kkes akk idiwenniyen", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Kkes asqerdec", "Delete chat?": "Tebɣiḍ ad tekkseḍ adiwenni?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Ur yessaweḍ ara ad yessukken aseɣwen", "Failed to create API Key.": "Ur yessaweḍ ara ad d-yesnulfu tasarut API.", + "Failed to delete calendar": "", "Failed to delete note": "Ur yessaweḍ ara ad yekkes tazmilt", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "Ur yessaweḍ ara ad d-yekkes agbur seg ufaylu: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Aklas", "Record voice": "Sekles taɣect", "Redirecting you to Open WebUI Community": "Aseḍfeṛ ar Temɣiwant n Open WebUI", @@ -1648,6 +1660,7 @@ "Relevance": "Tawatit", "Relevance Threshold": "", "Remember Dismissal": "Ccfawa ɣef ugdal", + "Reminder": "", "Remove": "Kkes", "Remove {{MODELID}} from list.": "Kkes {{MODELID}} seg wumuɣ.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Tazwara n ubadu", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Aya ad yekkes {NAME}} akked akk ayen yellan deg-s.", "This will delete all models including custom models": "Aya ad yekkes akk timudmin yellan gar-asent timudmin n tannumi", "This will delete all models including custom models and cannot be undone.": "Aya ad yekkes akk timudmin gar-asent timudmin tudmawanin yerna ur yezmir yiwen ad tent-id-yerr.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Aya ad yales taffa n tmussni u ad yemtawi akk ifuyla. Tebɣiḍ ad tkemmleḍ?", "Thorough explanation": "Asegzi leqqayen", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "Kkes asenteḍ", + "Unpin from Sidebar": "", "Unravel secrets": "Sban-d ayen yeffren", "Unshare Chat": "", "Unsupported file type.": "Tawsit n ufaylu ur tettusefrak ara.", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index 2c4d22b843..e29c402508 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "{{user}}의 채팅", "{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.", "*Prompt node ID(s) are required for image generation": "이미지 생성에는 프롬프트 노드 ID가 필요합니다.", + "1 hour before": "", "1 Source": "소스1", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "새로운 버전 (v{{LATEST_VERSION}})을 사용할 수 있습니다.", @@ -201,6 +206,7 @@ "Ask a question": "질문하기", "Assistant": "어시스턴트", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "지식 기반에서 파일 첨부", "Attach Files": "", "Attach Knowledge": "지식 기반 첨부", @@ -275,6 +281,7 @@ "Bypass Web Loader": "웹 콘텐츠 불러오기 생략", "Cache Base Model List": "기본 모델 목록 캐시", "Calendar": "캘린더", + "Calendar deleted": "", "Calendars": "", "Call": "음성 기능", "Call feature is not supported when using Web STT engine": "웹 STT 엔진 사용 시, 음성 기능은 지원되지 않습니다.", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "OpenAPI 호환 외부 도구 서버에 연결합니다.", "Connected ({{type}})": "", "Connection failed": "연결 실패", + "Connection lost. Reconnecting...": "", "Connection successful": "연결 성공", "Connection Type": "연결 방식", "Connections": "연결", @@ -524,6 +532,8 @@ "Delete All Chats": "모든 채팅 삭제", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "채팅 삭제", "Delete chat?": "채팅을 삭제하시겠습니까?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "링크 복사 실패", "Failed to create API Key.": "API 키 생성에 실패했습니다.", + "Failed to delete calendar": "", "Failed to delete note": "노트 삭제 실패", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "파일 내용 추출 실패: {{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "추론 난이도", "Reasoning Tags": "추론 태그", "Recently Used": "", + "Reconnected": "", "Record": "녹음", "Record voice": "음성 녹음", "Redirecting you to Open WebUI Community": "OpenWebUI 커뮤니티로 리디렉션 중", @@ -1647,6 +1659,7 @@ "Relevance": "관련도", "Relevance Threshold": "관련성 임계값", "Remember Dismissal": "다시 보지 않기", + "Reminder": "", "Remove": "삭제", "Remove {{MODELID}} from list.": "{{MODELID}}를 목록에서 제거.", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "새 대화 시작", "Start of the channel": "채널 시작", "Start Tag": "시작 태그", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "상태", "Status cleared successfully": "상태 초기화에 성공했습니다", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "{{NAME}}모든 내용을 삭제합니다.", "This will delete all models including custom models": "이렇게 하면 사용자 지정 모델을 포함한 모든 모델이 삭제됩니다", "This will delete all models including custom models and cannot be undone.": "이렇게 하면 사용자 지정 모델을 포함한 모든 모델이 삭제되며 실행 취소할 수 없습니다.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "지식 기반과 모든 파일 연동을 초기화합니다. 계속 하시겠습니까?", "Thorough explanation": "완전한 설명", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} 언로드", "Unlock mysteries": "미스터리 풀기", "Unpin": "고정 해제", + "Unpin from Sidebar": "", "Unravel secrets": "비밀 풀기", "Unshare Chat": "", "Unsupported file type.": "지원하지 않는 파일 형식", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index 0f2df1e48d..784832cde7 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "{{user}} susirašinėjimai", "{{webUIName}} Backend Required": "{{webUIName}} būtinas serveris", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -204,6 +209,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -278,6 +284,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Skambinti", "Call feature is not supported when using Web STT engine": "Skambučio funkcionalumas neleidžiamas naudojant Web STT variklį", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Ryšiai", @@ -527,6 +535,8 @@ "Delete All Chats": "Ištrinti visus pokalbius", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Ištrinti pokalbį", "Delete chat?": "Ištrinti pokalbį?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Nepavyko sukurti API rakto", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Įrašyti balsą", "Redirecting you to Open WebUI Community": "Perkeliam Jus į OpenWebUI bendruomenę", @@ -1650,6 +1662,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Pašalinti", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1898,7 +1911,13 @@ "Start a new conversation": "", "Start of the channel": "Kanalo pradžia", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "Platus paaiškinimas", "Thought": "", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "Atsemigti", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/lv-LV/translation.json b/src/lib/i18n/locales/lv-LV/translation.json index 9b28e934cf..09ae34c6a3 100644 --- a/src/lib/i18n/locales/lv-LV/translation.json +++ b/src/lib/i18n/locales/lv-LV/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "{{user}} tērzēšanas", "{{webUIName}} Backend Required": "Nepieciešama {{webUIName}} aizmugursistēma", "*Prompt node ID(s) are required for image generation": "*Attēla ģenerēšanai nepieciešami uzvednes mezgla ID", + "1 hour before": "", "1 Source": "1 avots", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Sadarbības kanāls, kurā cilvēki pievienojas kā dalībnieki", "A discussion channel where access is controlled by groups and permissions": "Diskusiju kanāls, kur piekļuvi kontrolē grupas un atļaujas", "A new version (v{{LATEST_VERSION}}) is now available.": "Ir pieejama jauna versija (v{{LATEST_VERSION}}).", @@ -203,6 +208,7 @@ "Ask a question": "Uzdot jautājumu", "Assistant": "Asistents", "Async Embedding Processing": "Asinhronā iegulšanas apstrāde", + "At time of event": "", "Attach File From Knowledge": "Pievienot failu no zināšanām", "Attach Files": "", "Attach Knowledge": "Pievienot zināšanau bāzi", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Apiet tīmekļa ielādētāju", "Cache Base Model List": "Kešot bāzes modeļu sarakstu", "Calendar": "Kalendārs", + "Calendar deleted": "", "Calendars": "", "Call": "Zvans", "Call feature is not supported when using Web STT engine": "Zvana funkcija nav atbalstīta, izmantojot Web STT dzinēju", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Savienojieties ar saviem OpenAPI saderīgajiem ārējo rīku serveriem.", "Connected ({{type}})": "", "Connection failed": "Savienojums neizdevās", + "Connection lost. Reconnecting...": "", "Connection successful": "Savienojums veiksmīgs", "Connection Type": "Savienojuma tips", "Connections": "Savienojumi", @@ -526,6 +534,8 @@ "Delete All Chats": "Dzēst visas tērzēšanas", "Delete all contents inside this folder": "Dzēst visu saturu šajā mapē", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Dzēst tērzēšanu", "Delete chat?": "Dzēst tērzēšanu?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Neizdevās nokopēt saiti", "Failed to create API Key.": "Neizdevās izveidot API atslēgu.", + "Failed to delete calendar": "", "Failed to delete note": "Neizdevās dzēst piezīmi", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "Neizdevās ekstrahēt saturu no faila: {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Spriedumu pūles", "Reasoning Tags": "Spriedumu tagi", "Recently Used": "", + "Reconnected": "", "Record": "Ierakstīt", "Record voice": "Ierakstīt balsi", "Redirecting you to Open WebUI Community": "Novirza jūs uz Open WebUI kopienu", @@ -1649,6 +1661,7 @@ "Relevance": "Atbilstība", "Relevance Threshold": "Atbilstības slieksnis", "Remember Dismissal": "Atcerēties noraidījumu", + "Reminder": "", "Remove": "Noņemt", "Remove {{MODELID}} from list.": "Noņemt {{MODELID}} no saraksta.", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Sākt jaunu sarunu", "Start of the channel": "Kanāla sākums", "Start Tag": "Sākuma tags", + "Starting in {{count}} minutes_zero": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "Statuss", "Status cleared successfully": "Statuss veiksmīgi notīrīts", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Tas dzēsīs {{NAME}} un visu tā saturu.", "This will delete all models including custom models": "Tas dzēsīs visus modeļus, ieskaitot pielāgotos modeļus", "This will delete all models including custom models and cannot be undone.": "Tas dzēsīs visus modeļus, ieskaitot pielāgotos modeļus, un to nevar atsaukt.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Tas atiestatīs zināšanu bāzi un sinhronizēs visus failus. Vai vēlaties turpināt?", "Thorough explanation": "Pamatīgs skaidrojums", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Izlādēs {{FROM_NOW}}", "Unlock mysteries": "Atklājiet noslēpumus", "Unpin": "Atspraust", + "Unpin from Sidebar": "", "Unravel secrets": "Atšķetiniet noslēpumus", "Unshare Chat": "", "Unsupported file type.": "Neatbalstīts faila tips.", diff --git a/src/lib/i18n/locales/ms-MY/translation.json b/src/lib/i18n/locales/ms-MY/translation.json index 7f23e7ed77..8dd75ac052 100644 --- a/src/lib/i18n/locales/ms-MY/translation.json +++ b/src/lib/i18n/locales/ms-MY/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "Perbualan {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Backend diperlukan", "*Prompt node ID(s) are required for image generation": "*ID nod Prompt diperlukan untuk penjanaan imej", + "1 hour before": "", "1 Source": "1 Sumber", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1m_masa_lalu", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Saluran kolaborasi di mana orang ramai menyertai sebagai ahli", "A discussion channel where access is controlled by groups and permissions": "Saluran perbincangan di mana akses dikawal oleh kumpulan dan kebenaran", "A new version (v{{LATEST_VERSION}}) is now available.": "Versi baru (v{{LATEST_VERSION}}) kini tersedia.", @@ -201,6 +206,7 @@ "Ask a question": "Tanya soalan", "Assistant": "Pembantu", "Async Embedding Processing": "Pemprosesan Embedding Tak Segerak", + "At time of event": "", "Attach File From Knowledge": "Lampirkan Fail Daripada Pengetahuan", "Attach Files": "", "Attach Knowledge": "Lampirkan Pengetahuan", @@ -275,6 +281,7 @@ "Bypass Web Loader": "Langkau Pemuat Web", "Cache Base Model List": "Senarai Model Asas Cache", "Calendar": "Kalendar", + "Calendar deleted": "", "Calendars": "", "Call": "Hubungi", "Call feature is not supported when using Web STT engine": "Ciri panggilan tidak disokong apabila menggunakan enjin Web STT", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Sambung ke pelayan alat luaran yang serasi dengan OpenAPI anda sendiri.", "Connected ({{type}})": "", "Connection failed": "Sambungan gagal", + "Connection lost. Reconnecting...": "", "Connection successful": "Sambungan berjaya", "Connection Type": "Jenis Sambungan", "Connections": "Sambungan", @@ -524,6 +532,8 @@ "Delete All Chats": "Padam Semua Perbualan", "Delete all contents inside this folder": "Padam semua kandungan dalam folder ini", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Padam Perbualan", "Delete chat?": "Padam perbualan?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "Gagal menyambung ke pelayan terminal {{URL}}", "Failed to copy link": "Gagal menyalin pautan", "Failed to create API Key.": "Gagal mencipta kekunci API", + "Failed to delete calendar": "", "Failed to delete note": "Gagal memadamkan nota", "Failed to download image": "Gagal memuat turun imej", "Failed to extract content from the file: {{error}}": "Gagal mengekstrak kandungan daripada fail: {{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "Usaha Penaakulan", "Reasoning Tags": "Tag Penaakulan", "Recently Used": "", + "Reconnected": "", "Record": "Rakaman", "Record voice": "Rakam suara", "Redirecting you to Open WebUI Community": "Membawa anda ke Komuniti OpenWebUI", @@ -1647,6 +1659,7 @@ "Relevance": "Perkaitan", "Relevance Threshold": "Ambang Perkaitan", "Remember Dismissal": "Ingat Penutupan", + "Reminder": "", "Remove": "Hapuskan", "Remove {{MODELID}} from list.": "Keluarkan {{MODELID}} daripada senarai.", "Remove action": "Keluarkan tindakan", @@ -1892,7 +1905,10 @@ "Start a new conversation": "Mulai perbualan baru", "Start of the channel": "Permulaan saluran", "Start Tag": "Tag Permulaan", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Kernel sedang dimulakan...", + "Starting now": "", "State": "", "Status": "Status", "Status cleared successfully": "Status telah dihapus dengan berjaya", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "Ini akan memadam {{NAME}} dan semua kandungannya.", "This will delete all models including custom models": "Ini akan memadam semua model termasuk model tersuai", "This will delete all models including custom models and cannot be undone.": "Ini akan memadam semua model termasuk model tersuai dan tidak boleh dibuat asal.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Ini akan menetapkan semula pangkalan pengetahuan dan menyegerakkan semua fail. Adakah anda ingin meneruskan?", "Thorough explanation": "Penjelasan menyeluruh", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "Membuang {{FROM_NOW}}", "Unlock mysteries": "Buka Misteri", "Unpin": "Nyahsematkan", + "Unpin from Sidebar": "", "Unravel secrets": "Ungkap Rahsia", "Unshare Chat": "Batalkan Perkongsian Sembang", "Unsupported file type.": "Jenis fail tidak disokong.", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index ebfff87340..7f45c82157 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} sine samtaler", "{{webUIName}} Backend Required": "Backend til {{webUIName}} kreves", "*Prompt node ID(s) are required for image generation": "Node-ID-er for ledetekst kreves for generering av bilder", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "En ny versjon (v{{LATEST_VERSION}}) er nå tilgjengelig.", @@ -202,6 +207,7 @@ "Ask a question": "Still et spørsmål", "Assistant": "Assistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Kalender", + "Calendar deleted": "", "Calendars": "", "Call": "Ring", "Call feature is not supported when using Web STT engine": "Ringefunksjonen støttes ikke når du bruker Web STT-motoren", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Tilkoblinger", @@ -525,6 +533,8 @@ "Delete All Chats": "Slett alle chatter", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Slett chat", "Delete chat?": "Slette chat?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Kan ikke opprette en API-nøkkel.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Resonneringsinnsats", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Ta opp tale", "Redirecting you to Open WebUI Community": "Omdirigerer deg til OpenWebUI-fellesskapet", @@ -1648,6 +1660,7 @@ "Relevance": "Relevans", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Fjern", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Starten av kanalen", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Dette sletter {{NAME}} og alt innholdet.", "This will delete all models including custom models": "Dette sletter alle modeller, inkludert tilpassede modeller", "This will delete all models including custom models and cannot be undone.": "Dette sletter alle modeller, inkludert tilpassede modeller, og kan ikke angres.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Dette tilbakestiller kunnskapsbasen og synkroniserer alle filer. Vil du fortsette?", "Thorough explanation": "Grundig forklaring", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Lås opp mysterier", "Unpin": "Løsne", + "Unpin from Sidebar": "", "Unravel secrets": "Avslør hemmeligheter", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 3458ccf314..4651d33fc5 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}'s chats", "{{webUIName}} Backend Required": "{{webUIName}} Backend verplicht", "*Prompt node ID(s) are required for image generation": "*Prompt node ID('s) zijn vereist voor het genereren van afbeeldingen", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Een nieuwe versie (v{{LATEST_VERSION}}) is nu beschikbaar", @@ -202,6 +207,7 @@ "Ask a question": "Stel een vraag", "Assistant": "Assistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Agenda", + "Calendar deleted": "", "Calendars": "", "Call": "Oproep", "Call feature is not supported when using Web STT engine": "Belfunctie wordt niet ondersteund bij gebruik van de Web STT engine", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Verbind met je eigen OpenAPI-compatibele externe gereedschapservers", "Connected ({{type}})": "", "Connection failed": "Connectie mislukt", + "Connection lost. Reconnecting...": "", "Connection successful": "Connectie succesvol", "Connection Type": "Connectie type", "Connections": "Verbindingen", @@ -525,6 +533,8 @@ "Delete All Chats": "Verwijder alle chats", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Verwijder chat", "Delete chat?": "Verwijder chat?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Kan API Key niet aanmaken.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Redeneerinspanning", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Neem stem op", "Redirecting you to Open WebUI Community": "Je wordt doorgestuurd naar OpenWebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "Relevantie", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Verwijderen", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Begin van het kanaal", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Dit zal {{NAME}} verwijderen en al zijn inhoud.", "This will delete all models including custom models": "Dit zal alle modellen, ook aangepaste modellen, verwijderen", "This will delete all models including custom models and cannot be undone.": "Dit zal alle modellen, ook aangepaste modellen, verwijderen en kan niet ongedaan worden gemaakt", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Dit zal de kennisdatabase resetten en alle bestanden synchroniseren. Wilt u doorgaan?", "Thorough explanation": "Grondige uitleg", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Ontsleutel mysteries", "Unpin": "Losmaken", + "Unpin from Sidebar": "", "Unravel secrets": "Ontrafel geheimen", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index c6d006cb1a..d29adc4b55 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} ਦੀਆਂ ਗੱਲਾਂ", "{{webUIName}} Backend Required": "{{webUIName}} ਬੈਕਐਂਡ ਲੋੜੀਂਦਾ ਹੈ", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "ਕਨੈਕਸ਼ਨ", @@ -525,6 +533,8 @@ "Delete All Chats": "ਸਾਰੀਆਂ ਚੈਟਾਂ ਨੂੰ ਮਿਟਾਓ", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "ਗੱਲਬਾਤ ਮਿਟਾਓ", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "API ਕੁੰਜੀ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ।", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "ਆਵਾਜ਼ ਰਿਕਾਰਡ ਕਰੋ", "Redirecting you to Open WebUI Community": "ਤੁਹਾਨੂੰ ਓਪਨਵੈਬਯੂਆਈ ਕਮਿਊਨਿਟੀ ਵੱਲ ਰੀਡਾਇਰੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "ਹਟਾਓ", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "ਚੈਨਲ ਦੀ ਸ਼ੁਰੂਆਤ", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "ਵਿਸਥਾਰ ਨਾਲ ਵਿਆਖਿਆ", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index 1dff552813..7143c47bc7 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "Czaty użytkownika {{user}}", "{{webUIName}} Backend Required": "Wymagany backend {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*Do generowania obrazów wymagane jest ID węzła promptu", + "1 hour before": "", "1 Source": "1 źródło", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Kanał współpracy, do którego użytkownicy dołączają jako członkowie", "A discussion channel where access is controlled by groups and permissions": "Kanał dyskusyjny, do którego dostęp jest kontrolowany przez grupy i uprawnienia", "A new version (v{{LATEST_VERSION}}) is now available.": "Dostępna jest nowa wersja (v{{LATEST_VERSION}}).", @@ -204,6 +209,7 @@ "Ask a question": "Zadaj pytanie", "Assistant": "Asystent", "Async Embedding Processing": "Asynchroniczne przetwarzanie embeddingów", + "At time of event": "", "Attach File From Knowledge": "Dołącz plik z bazy wiedzy", "Attach Files": "", "Attach Knowledge": "Dołącz bazę wiedzy", @@ -278,6 +284,7 @@ "Bypass Web Loader": "Pomiń Web Loader", "Cache Base Model List": "Cachuj listę modeli bazowych", "Calendar": "Kalendarz", + "Calendar deleted": "", "Calendars": "", "Call": "Rozmowa", "Call feature is not supported when using Web STT engine": "Funkcja rozmowy nie jest obsługiwana przy użyciu przeglądarkowego silnika STT", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Połącz z własnymi serwerami narzędzi zgodnymi z OpenAPI.", "Connected ({{type}})": "", "Connection failed": "Połączenie nieudane", + "Connection lost. Reconnecting...": "", "Connection successful": "Połączenie udane", "Connection Type": "Typ połączenia", "Connections": "Połączenia", @@ -527,6 +535,8 @@ "Delete All Chats": "Usuń wszystkie czaty", "Delete all contents inside this folder": "Usuń całą zawartość tego folderu", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Usuń czat", "Delete chat?": "Usunąć czat?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Nie udało się skopiować linku", "Failed to create API Key.": "Nie udało się utworzyć klucza API.", + "Failed to delete calendar": "", "Failed to delete note": "Nie udało się usunąć notatki", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "Nie udało się wyodrębnić treści z pliku: {{error}}", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "Reasoning Effort", "Reasoning Tags": "Reasoning Tags", "Recently Used": "", + "Reconnected": "", "Record": "Nagraj", "Record voice": "Nagraj głos", "Redirecting you to Open WebUI Community": "Przekierowanie do społeczności Open WebUI", @@ -1650,6 +1662,7 @@ "Relevance": "Trafność", "Relevance Threshold": "Próg trafności", "Remember Dismissal": "Zapamiętaj odrzucenie", + "Reminder": "", "Remove": "Usuń", "Remove {{MODELID}} from list.": "Usuń {{MODELID}} z listy.", "Remove action": "", @@ -1898,7 +1911,13 @@ "Start a new conversation": "Rozpocznij nową rozmowę", "Start of the channel": "Początek kanału", "Start Tag": "Tag startowy", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "Status", "Status cleared successfully": "Status wyczyszczony pomyślnie", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "To usunie {{NAME}} i całą zawartość.", "This will delete all models including custom models": "To usunie wszystkie modele (w tym własne).", "This will delete all models including custom models and cannot be undone.": "To usunie wszystkie modele i jest nieodwracalne.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "To zresetuje bazę wiedzy i zsynchronizuje pliki. Kontynuować?", "Thorough explanation": "Dokładne wyjaśnienie", "Thought": "", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "Odładowuje za {{FROM_NOW}}", "Unlock mysteries": "Odkrywaj tajemnice", "Unpin": "Odepnij", + "Unpin from Sidebar": "", "Unravel secrets": "Rozwiązuj zagadki", "Unshare Chat": "", "Unsupported file type.": "Nieobsługiwany typ pliku.", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 0954c0a91a..bde1024811 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Chats de {{user}}", "{{webUIName}} Backend Required": "Backend {{webUIName}} necessário", "*Prompt node ID(s) are required for image generation": "*Prompt node ID(s) são obrigatórios para gerar imagens", + "1 hour before": "", "1 Source": "1 Origem", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1m atrás", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Um canal de colaboração onde as pessoas se juntam como membros.", "A discussion channel where access is controlled by groups and permissions": "Um canal de discussão onde o acesso é controlado por grupos e permissões.", "A new version (v{{LATEST_VERSION}}) is now available.": "Uma nova versão (v{{LATEST_VERSION}}) está disponível.", @@ -203,6 +208,7 @@ "Ask a question": "Faça uma pergunta", "Assistant": "Assistente", "Async Embedding Processing": "Processamento de Embedding assíncrono", + "At time of event": "", "Attach File From Knowledge": "Anexar arquivo da base de conhecimento", "Attach Files": "Anexar arquivos", "Attach Knowledge": "Anexar Base de Conhecimento", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Ignorar carregador da Web", "Cache Base Model List": "Lista de modelos base de cache", "Calendar": "Calendário", + "Calendar deleted": "", "Calendars": "", "Call": "Chamada", "Call feature is not supported when using Web STT engine": "O recurso de chamada não é suportado ao usar o mecanismo Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Conecte-se aos seus próprios servidores de ferramentas externas compatíveis com OpenAPI.", "Connected ({{type}})": "Conectado ({{type}})", "Connection failed": "Falha na conexão", + "Connection lost. Reconnecting...": "", "Connection successful": "Conexão bem-sucedida", "Connection Type": "Tipo de conexão", "Connections": "Conexões", @@ -526,6 +534,8 @@ "Delete All Chats": "Excluir Todos os Chats", "Delete all contents inside this folder": "Apague todo o conteúdo desta pasta.", "Delete automation?": "Excluir automação?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Excluir Chat", "Delete chat?": "Excluir chat?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "Falha ao conectar ao servidor de terminal {{URL}}", "Failed to copy link": "Falha ao copiar o link", "Failed to create API Key.": "Falha ao criar a Chave API.", + "Failed to delete calendar": "", "Failed to delete note": "Falha ao excluir a nota", "Failed to download image": "Falha ao baixar a imagem", "Failed to extract content from the file: {{error}}": "Falha ao extrair conteúdo do arquivo: {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Esforço de raciocínio", "Reasoning Tags": "Tags de raciocínio", "Recently Used": "Usado recentemente", + "Reconnected": "", "Record": "Gravar", "Record voice": "Gravar voz", "Redirecting you to Open WebUI Community": "Redirecionando você para a Comunidade OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Relevância", "Relevance Threshold": "Limiar de Relevância", "Remember Dismissal": "Lembrar da dispensa", + "Reminder": "", "Remove": "Remover", "Remove {{MODELID}} from list.": "Remover {{MODELID}} da lista.", "Remove action": "Remover ação", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Iniciar uma nova conversa", "Start of the channel": "Início do canal", "Start Tag": "Tag inicial", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Iniciando kernel...", + "Starting now": "", "State": "Estado", "Status": "Status", "Status cleared successfully": "Status liberado com sucesso", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Esta ação excluirá {{NAME}} e todos seus conteúdos.", "This will delete all models including custom models": "Isto vai excluir todos os modelos, incluindo personalizados", "This will delete all models including custom models and cannot be undone.": "Isto vai excluir todos os modelos, incluindo personalizados e não pode ser desfeito.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Esta ação resetará a base de conhecimento e sincronizará todos os arquivos. Deseja continuar?", "Thorough explanation": "Explicação detalhada", "Thought": "Pensamento", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Descarrega {{FROM_NOW}}", "Unlock mysteries": "Desvendar mistérios", "Unpin": "Desfixar", + "Unpin from Sidebar": "", "Unravel secrets": "Desvendar segredos", "Unshare Chat": "Cancelar compartilhamento do chat", "Unsupported file type.": "Tipo de arquivo não suportado.", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index c8f23d1dea..1b9c2e7e48 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Chats de {{user}}", "{{webUIName}} Backend Required": "Backend {{webUIName}} Necessário", "*Prompt node ID(s) are required for image generation": "*ID(s) do nó de prompt são necessários para a geração de imagem", + "1 hour before": "", "1 Source": "Uma Fonte", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "há 1 minuto", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Um canal de colaboração onde as pessoas entram como membros", "A discussion channel where access is controlled by groups and permissions": "Um canal de discussão onde o acesso é controlado por grupos e permissões", "A new version (v{{LATEST_VERSION}}) is now available.": "Uma nova versão (v{{LATEST_VERSION}}) está agora disponível.", @@ -203,6 +208,7 @@ "Ask a question": "Fazer uma pergunta", "Assistant": "Assistente", "Async Embedding Processing": "Incorporação de Processamento Assíncrono", + "At time of event": "", "Attach File From Knowledge": "Anexar Ficheiro do Conhecimento", "Attach Files": "", "Attach Knowledge": "Anexar Conhecimento", @@ -277,6 +283,7 @@ "Bypass Web Loader": "Ignorar Carregador Web", "Cache Base Model List": "Cache da Lista de Modelos Base", "Calendar": "Calendário", + "Calendar deleted": "", "Calendars": "", "Call": "Chamar", "Call feature is not supported when using Web STT engine": "A funcionalide de Chamar não é suportada quando usa um motor Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Ligar ao seu próprio servidor de ferramentas externo compatível com a OpenAI.", "Connected ({{type}})": "", "Connection failed": "Ligação falhou", + "Connection lost. Reconnecting...": "", "Connection successful": "Ligação bem sucedida", "Connection Type": "Tipo de ligação", "Connections": "Ligações", @@ -526,6 +534,8 @@ "Delete All Chats": "Apagar todas as conversas", "Delete all contents inside this folder": "Apagar todo o conteúdo dentro desta pasta", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Apagar Conversa", "Delete chat?": "Apagar conversa?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "Falha na ligação ao terminal de servidores {{URL}}", "Failed to copy link": "Falha ao copiar a hiperligação", "Failed to create API Key.": "Falha ao criar a Chave da API.", + "Failed to delete calendar": "", "Failed to delete note": "Falha ao apagar a nota", "Failed to download image": "Falha ao transferir a imagem", "Failed to extract content from the file: {{error}}": "Falha ao extrair conteúdo do ficheiro: {{error}}", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Esforço de Raciocínio", "Reasoning Tags": "Etiquetas de Raciocínio", "Recently Used": "", + "Reconnected": "", "Record": "Gravar", "Record voice": "Gravar voz", "Redirecting you to Open WebUI Community": "Redirecionando-o para a Comunidade OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Relevância", "Relevance Threshold": "Limite de Relevância", "Remember Dismissal": "Lembrar Descartar", + "Reminder": "", "Remove": "Remover", "Remove {{MODELID}} from list.": "Remover {{MODELID}} da lista.", "Remove action": "Remover ação", @@ -1896,7 +1909,12 @@ "Start a new conversation": "Iniciar uma nova conversa", "Start of the channel": "Início do canal", "Start Tag": "Início da Tag", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Iniciando kernel...", + "Starting now": "", "State": "", "Status": "Estado", "Status cleared successfully": "Estado limpo com sucesso", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Isto irá excluir {{NAME}} e todo o seu conteúdo.", "This will delete all models including custom models": "Isto irá excluir todos os modelos, incluindo os modelos personalizados", "This will delete all models including custom models and cannot be undone.": "Isto irá excluir todos os modelos, incluindo os modelos personalizados, e não pode ser desfeito.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Isto irá redefinir a base de conhecimento e sincronizar todos os arquivos. Deseja continuar?", "Thorough explanation": "Explicação Minuciosa", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "Descarreva {{FROM_NOW}}", "Unlock mysteries": "Desbloquear Mistérios", "Unpin": "Desafixar", + "Unpin from Sidebar": "", "Unravel secrets": "Desvendar segredos", "Unshare Chat": "Parar partilha de conversa", "Unsupported file type.": "Tipo de ficheiro não suportado", diff --git a/src/lib/i18n/locales/ro-RO/translation.json b/src/lib/i18n/locales/ro-RO/translation.json index 0215a6eacc..3028840716 100644 --- a/src/lib/i18n/locales/ro-RO/translation.json +++ b/src/lib/i18n/locales/ro-RO/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Conversațiile lui {{user}}", "{{webUIName}} Backend Required": "Este necesar backend-ul {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*Sunt necesare ID-urile nodurilor de solicitare pentru generarea imaginii*", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "O nouă versiune (v{{LATEST_VERSION}}) este acum disponibilă.", @@ -203,6 +208,7 @@ "Ask a question": "Pune o întrebare", "Assistant": "Asistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Apel", "Call feature is not supported when using Web STT engine": "Funcția de apel nu este suportată când se utilizează motorul Web STT", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "Conexiune eșuată", + "Connection lost. Reconnecting...": "", "Connection successful": "Conexiune reușită", "Connection Type": "Tip conexiune", "Connections": "Conexiuni", @@ -526,6 +534,8 @@ "Delete All Chats": "Șterge Toate Conversațiile", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Șterge Conversația", "Delete chat?": "Șterge conversația?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Crearea cheii API a eșuat.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Înregistrează vocea", "Redirecting you to Open WebUI Community": "Vă redirecționăm către Comunitatea OpenWebUI", @@ -1649,6 +1661,7 @@ "Relevance": "Relevanță", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Înlătură", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Începutul canalului", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Acest lucru va șterge {{NAME}} și toate conținuturile sale.", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Aceasta va reseta baza de cunoștințe și va sincroniza toate fișierele. Doriți să continuați?", "Thorough explanation": "Explicație detaliată", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "Anulează Fixarea", + "Unpin from Sidebar": "", "Unravel secrets": "Dezvăluie secretele", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index 15663aecc0..954d8f7f4e 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "Чаты {{user}}'а", "{{webUIName}} Backend Required": "Необходимо подключение к серверу {{webUIName}}", "*Prompt node ID(s) are required for image generation": "ID узлов промптов обязательны для генерации изображения", + "1 hour before": "", "1 Source": "1 Источник", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1 мин назад", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "Канал для совместной работы с присоединением участников", "A discussion channel where access is controlled by groups and permissions": "Обсуждение канала, где доступ контролируется группами и разрешениями", "A new version (v{{LATEST_VERSION}}) is now available.": "Новая версия (v{{LATEST_VERSION}}) теперь доступна.", @@ -204,6 +209,7 @@ "Ask a question": "Задать вопрос", "Assistant": "Ассистент", "Async Embedding Processing": "Асинхронная обработка эмбеддингов", + "At time of event": "", "Attach File From Knowledge": "Прикрепить файл из знаний", "Attach Files": "", "Attach Knowledge": "Прикрепить знания", @@ -278,6 +284,7 @@ "Bypass Web Loader": "Обход веб-загрузчика", "Cache Base Model List": "Кэшировать список базовых моделей", "Calendar": "Календарь", + "Calendar deleted": "", "Calendars": "", "Call": "Вызов", "Call feature is not supported when using Web STT engine": "Функция вызова не поддерживается при использовании Web STT (распознавание речи) движка", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Подключитесь к вашим собственным внешним инструментальным серверам, совместимым с OpenAPI.", "Connected ({{type}})": "Подключено ({{type}})", "Connection failed": "Подключение не удалось", + "Connection lost. Reconnecting...": "", "Connection successful": "Успешное подключение", "Connection Type": "Тип подключения", "Connections": "Подключения", @@ -527,6 +535,8 @@ "Delete All Chats": "Удалить ВСЕ Чаты", "Delete all contents inside this folder": "Удалить все содержимое внутри этой папки", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Удалить Чат", "Delete chat?": "Удалить чат?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "Не удалось подключиться к серверу терминала {{URL}}", "Failed to copy link": "Не удалось скопировать ссылку", "Failed to create API Key.": "Не удалось создать ключ API.", + "Failed to delete calendar": "", "Failed to delete note": "Не удалось удалить заметку", "Failed to download image": "Не удалось загрузить изображение", "Failed to extract content from the file: {{error}}": "Не удалось извлечь содержимое из файла: {{error}}", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "Усилия для рассуждения", "Reasoning Tags": "Теги рассуждения", "Recently Used": "", + "Reconnected": "", "Record": "Запись", "Record voice": "Записать голос", "Redirecting you to Open WebUI Community": "Перенаправляем вас в сообщество OpenWebUI", @@ -1650,6 +1662,7 @@ "Relevance": "Релевантность", "Relevance Threshold": "Порог релевантности", "Remember Dismissal": "Запомнить отклонение", + "Reminder": "", "Remove": "Удалить", "Remove {{MODELID}} from list.": "Удалить {{MODELID}} из списка.", "Remove action": "Удалить действие", @@ -1898,7 +1911,13 @@ "Start a new conversation": "Начать новый разговор", "Start of the channel": "Начало канала", "Start Tag": "Начальный тег", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Запуск ядра...", + "Starting now": "", "State": "", "Status": "Статус", "Status cleared successfully": "Статус успешно очищен", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "При этом будет удален {{NAME}} и все его содержимое.", "This will delete all models including custom models": "Это приведет к удалению всех моделей, включая пользовательские модели.", "This will delete all models including custom models and cannot be undone.": "При этом будут удалены все модели, включая пользовательские, и это действие нельзя будет отменить.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Это сбросит базу знаний и синхронизирует все файлы. Хотите продолжить?", "Thorough explanation": "Подробное объяснение", "Thought": "Рассуждение", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "Выгрузка из памяти {{FROM_NOW}}", "Unlock mysteries": "Разблокируйте тайны", "Unpin": "Открепить", + "Unpin from Sidebar": "", "Unravel secrets": "Разгадать секреты", "Unshare Chat": "Отменить публикацию чата", "Unsupported file type.": "Неподдерживаемый тип файла.", diff --git a/src/lib/i18n/locales/sk-SK/translation.json b/src/lib/i18n/locales/sk-SK/translation.json index 74d16d847c..0ecf193014 100644 --- a/src/lib/i18n/locales/sk-SK/translation.json +++ b/src/lib/i18n/locales/sk-SK/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "{{user}}'s konverzácie", "{{webUIName}} Backend Required": "Vyžaduje sa {{webUIName}} Backend", "*Prompt node ID(s) are required for image generation": "*Sú potrebné IDs pre prompt node na generovanie obrázkov", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Nová verzia (v{{LATEST_VERSION}}) je teraz k dispozícii.", @@ -204,6 +209,7 @@ "Ask a question": "Opýtajte sa otázku", "Assistant": "Asistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Pripojiť znalosti", @@ -278,6 +284,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Volanie", "Call feature is not supported when using Web STT engine": "Funkcia volania nie je podporovaná pri použití Web STT engine.", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Pripojenia", @@ -527,6 +535,8 @@ "Delete All Chats": "Odstrániť všetky konverzácie", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Odstrániť chat", "Delete chat?": "Odstrániť konverzáciu?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Nepodarilo sa vytvoriť API kľúč.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Nahrať hlas", "Redirecting you to Open WebUI Community": "Presmerovanie na komunitu OpenWebUI", @@ -1650,6 +1662,7 @@ "Relevance": "Relevancia", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Odstrániť", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1898,7 +1911,13 @@ "Start a new conversation": "", "Start of the channel": "Začiatok kanála", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "Týmto dôjde k odstráneniu {{NAME}} a všetkých jeho obsahov.", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Toto obnoví znalostnú databázu a synchronizuje všetky súbory. Prajete si pokračovať?", "Thorough explanation": "Obsiahle vysvetlenie", "Thought": "", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "Odopnúť", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index fd5e72e1fb..647eb187b5 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -34,8 +34,13 @@ "{{user}}'s Chats": "Ћаскања корисника {{user}}", "{{webUIName}} Backend Required": "Захтева се {{webUIName}} позадинац", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -203,6 +208,7 @@ "Ask a question": "Постави питање", "Assistant": "Помоћник", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -277,6 +283,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "Позив", "Call feature is not supported when using Web STT engine": "", @@ -414,6 +421,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Везе", @@ -526,6 +534,8 @@ "Delete All Chats": "Обриши сва ћаскања", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Обриши ћаскање", "Delete chat?": "Обрисати ћаскање?", "Delete Event": "", @@ -887,6 +897,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Неуспешно стварање API кључа.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1626,6 +1637,7 @@ "Reasoning Effort": "Јачина размишљања", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Сними глас", "Redirecting you to Open WebUI Community": "Преусмеравање на OpenWebUI заједницу", @@ -1649,6 +1661,7 @@ "Relevance": "Примењивост", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Уклони", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1896,7 +1909,12 @@ "Start a new conversation": "", "Start of the channel": "Почетак канала", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2009,6 +2027,7 @@ "This will delete {{NAME}} and all its contents.": "Ово ће обрисати {{NAME}} и сав садржај унутар.", "This will delete all models including custom models": "Ово ће обрисати све моделе укључујући прилагођене моделе", "This will delete all models including custom models and cannot be undone.": "Ово ће обрисати све моделе укључујући прилагођене моделе и не може се опозвати.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Ово ће обрисати базу знања и ускладити све датотеке. Да ли желите наставити?", "Thorough explanation": "Детаљно објашњење", "Thought": "", @@ -2095,6 +2114,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Реши мистерије", "Unpin": "Откачи", + "Unpin from Sidebar": "", "Unravel secrets": "Разоткриј тајне", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index d75b41b928..9915d73f25 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}s Chattar", "{{webUIName}} Backend Required": "{{webUIName}} Backend krävs", "*Prompt node ID(s) are required for image generation": "*Prompt node ID(s) krävs för bildgenerering", + "1 hour before": "", "1 Source": "1 källa", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "En ny version (v{{LATEST_VERSION}}) är nu tillgänglig.", @@ -202,6 +207,7 @@ "Ask a question": "Ställ en fråga", "Assistant": "Assistent", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "Bifoga kunskap", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Kringgå webbläsare", "Cache Base Model List": "", "Calendar": "Kalender", + "Calendar deleted": "", "Calendars": "", "Call": "Samtal", "Call feature is not supported when using Web STT engine": "Samtalsfunktionen är inte kompatibel med Web Tal-till-text motor", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Anslut till dina egna OpenAPI-kompatibla externa verktygsservrar.", "Connected ({{type}})": "", "Connection failed": "Anslutning misslyckades", + "Connection lost. Reconnecting...": "", "Connection successful": "Anslutning lyckades", "Connection Type": "Anslutningstyp", "Connections": "Anslutningar", @@ -525,6 +533,8 @@ "Delete All Chats": "Ta bort alla chattar", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Radera chatt", "Delete chat?": "Radera chatt?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Misslyckades med att kopiera länk", "Failed to create API Key.": "Misslyckades med att skapa API-nyckel.", + "Failed to delete calendar": "", "Failed to delete note": "Misslyckades med att ta bort anteckning", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Resonemangsinsats", "Reasoning Tags": "Resonemangs-taggar (tags)", "Recently Used": "", + "Reconnected": "", "Record": "Spela in", "Record voice": "Spela in röst", "Redirecting you to Open WebUI Community": "Omdirigerar dig till OpenWebUI Community", @@ -1648,6 +1660,7 @@ "Relevance": "Relevans", "Relevance Threshold": "Relevanströskel", "Remember Dismissal": "Kom ihåg avvisning", + "Reminder": "", "Remove": "Ta bort", "Remove {{MODELID}} from list.": "Ta bort {{MODELID}} från listan.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Starta en ny konversation", "Start of the channel": "Början av kanalen", "Start Tag": "Starta en tagg", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Detta kommer att radera {{NAME}} och allt dess innehåll.", "This will delete all models including custom models": "Detta kommer att radera alla modeller inklusive anpassade modeller", "This will delete all models including custom models and cannot be undone.": "Detta kommer att radera alla modeller inklusive anpassade modeller och kan inte ångras.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Detta kommer att återställa kunskapsbasen och synkronisera alla filer. Vill du fortsätta?", "Thorough explanation": "Djupare förklaring", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "Avlastar {{FROM_NOW}}", "Unlock mysteries": "Lås upp mysterier", "Unpin": "Ta bort fästning", + "Unpin from Sidebar": "", "Unravel secrets": "Avslöja hemligheter", "Unshare Chat": "", "Unsupported file type.": "Filtypen stöds inte.", diff --git a/src/lib/i18n/locales/ta-IN/translation.json b/src/lib/i18n/locales/ta-IN/translation.json index 8e5af4f6e2..646aec1471 100644 --- a/src/lib/i18n/locales/ta-IN/translation.json +++ b/src/lib/i18n/locales/ta-IN/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} இன் அரட்டைகள்", "{{webUIName}} Backend Required": "{{webUIName}} பின்தளம் தேவை", "*Prompt node ID(s) are required for image generation": "*பட உருவாக்கத்திற்கு உடனடி முனை ID(கள்) தேவை", + "1 hour before": "", "1 Source": "1 ஆதாரம்", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1 நிமிடம் முன்", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "மக்கள் உறுப்பினர்களாக சேரும் ஒத்துழைப்பு சேனல்", "A discussion channel where access is controlled by groups and permissions": "குழுக்கள் மற்றும் அனுமதிகளால் அணுகல் கட்டுப்படுத்தப்படும் விவாத சேனல்", "A new version (v{{LATEST_VERSION}}) is now available.": "புதிய பதிப்பு (v{{LATEST_VERSION}}) இப்போது கிடைக்கிறது.", @@ -202,6 +207,7 @@ "Ask a question": "ஒரு கேள்வி கேளுங்கள்", "Assistant": "உதவியாளர்", "Async Embedding Processing": "ஒத்திசைவு உட்பொதித்தல் செயலாக்கம்", + "At time of event": "", "Attach File From Knowledge": "அறிவிலிருந்து கோப்பை இணைக்கவும்", "Attach Files": "கோப்புகளை இணைக்கவும்", "Attach Knowledge": "அறிவை இணைக்கவும்", @@ -276,6 +282,7 @@ "Bypass Web Loader": "பைபாஸ் இணைய ஏற்றி", "Cache Base Model List": "கேச் அடிப்படை மாதிரி பட்டியல்", "Calendar": "நாட்காட்டி", + "Calendar deleted": "", "Calendars": "", "Call": "அழைக்கவும்", "Call feature is not supported when using Web STT engine": "Web STT இன்ஜினைப் பயன்படுத்தும் போது அழைப்பு அம்சம் ஆதரிக்கப்படாது", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "உங்கள் சொந்த OpenAPI இணக்கமான வெளிப்புற கருவி சேவையகங்களுடன் இணைக்கவும்.", "Connected ({{type}})": "இணைக்கப்பட்டது ({{type}})", "Connection failed": "இணைப்பு தோல்வியடைந்தது", + "Connection lost. Reconnecting...": "", "Connection successful": "இணைப்பு வெற்றிகரமாக உள்ளது", "Connection Type": "இணைப்பு வகை", "Connections": "இணைப்புகள்", @@ -525,6 +533,8 @@ "Delete All Chats": "அனைத்து அரட்டைகளையும் நீக்கு", "Delete all contents inside this folder": "இந்தக் கோப்புறையில் உள்ள அனைத்து உள்ளடக்கங்களையும் நீக்கவும்", "Delete automation?": "தானியக்கத்தை நீக்கவா?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "அரட்டையை நீக்கு", "Delete chat?": "அரட்டையை நீக்கவா?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "{{URL}} டெர்மினல் சர்வருடன் இணைக்க முடியவில்லை", "Failed to copy link": "இணைப்பை நகலெடுக்க முடியவில்லை", "Failed to create API Key.": "API விசையை உருவாக்குவதில் தோல்வி.", + "Failed to delete calendar": "", "Failed to delete note": "குறிப்பை நீக்க முடியவில்லை", "Failed to download image": "படத்தைப் பதிவிறக்க முடியவில்லை", "Failed to extract content from the file: {{error}}": "கோப்பிலிருந்து உள்ளடக்கத்தைப் பிரித்தெடுக்க முடியவில்லை: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "பகுத்தறிவு முயற்சி", "Reasoning Tags": "பகுத்தறிவு குறிச்சொற்கள்", "Recently Used": "சமீபத்தில் பயன்படுத்தப்பட்டது", + "Reconnected": "", "Record": "பதிவு", "Record voice": "குரல் பதிவு", "Redirecting you to Open WebUI Community": "உங்களை Open WebUI சமூகத்திற்கு திருப்பி விடுகிறோம்", @@ -1648,6 +1660,7 @@ "Relevance": "சம்பந்தம்", "Relevance Threshold": "சம்பந்தமான வரம்பு", "Remember Dismissal": "பணிநீக்கம் என்பதை நினைவில் கொள்க", + "Reminder": "", "Remove": "அகற்று", "Remove {{MODELID}} from list.": "பட்டியலில் இருந்து {{MODELID}} ஐ அகற்று.", "Remove action": "செயலை அகற்று", @@ -1894,7 +1907,11 @@ "Start a new conversation": "புதிய உரையாடலைத் தொடங்கவும்", "Start of the channel": "சேனலின் ஆரம்பம்", "Start Tag": "தொடக்க குறிச்சொல்", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "கர்னலைத் தொடங்குகிறது...", + "Starting now": "", "State": "நிலை", "Status": "நிலை", "Status cleared successfully": "நிலை வெற்றிகரமாக அழிக்கப்பட்டது", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "இது {{NAME}} மற்றும் அதன் அனைத்து உள்ளடக்கங்களையும் நீக்கும்.", "This will delete all models including custom models": "இது தனிப்பயன் மாதிரிகள் உட்பட அனைத்து மாடல்களையும் நீக்கும்", "This will delete all models including custom models and cannot be undone.": "இது தனிப்பயன் மாதிரிகள் உட்பட அனைத்து மாடல்களையும் நீக்கிவிடும், மேலும் செயல்தவிர்க்க முடியாது.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "இது அறிவுத் தளத்தை மீட்டமைத்து அனைத்து கோப்புகளையும் ஒத்திசைக்கும். நீங்கள் தொடர விரும்புகிறீர்களா?", "Thorough explanation": "விரிவான விளக்கம்", "Thought": "சிந்தனை", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} இறக்குகிறது", "Unlock mysteries": "மர்மங்களைத் திறக்கவும்", "Unpin": "அன்பின்", + "Unpin from Sidebar": "", "Unravel secrets": "இரகசியங்களை அவிழ்த்து விடுங்கள்", "Unshare Chat": "அரட்டையைப் பகிர்வதை நீக்கு", "Unsupported file type.": "ஆதரிக்கப்படாத கோப்பு வகை.", diff --git a/src/lib/i18n/locales/th-TH/translation.json b/src/lib/i18n/locales/th-TH/translation.json index 733bd77d5e..17dd64ef45 100644 --- a/src/lib/i18n/locales/th-TH/translation.json +++ b/src/lib/i18n/locales/th-TH/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "การแชทของ {{user}}", "{{webUIName}} Backend Required": "ต้องใช้ Backend ของ {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*ต้องระบุ ID ของ prompt node สำหรับการสร้างภาพ", + "1 hour before": "", "1 Source": "1 แหล่งที่มา", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "เวอร์ชันใหม่ (v{{LATEST_VERSION}}) พร้อมให้ใช้งานแล้ว", @@ -201,6 +206,7 @@ "Ask a question": "ถามคำถาม", "Assistant": "ผู้ช่วย", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "แนบไฟล์จากฐานความรู้", "Attach Files": "", "Attach Knowledge": "แนบฐานความรู้", @@ -275,6 +281,7 @@ "Bypass Web Loader": "ข้ามตัวโหลดเว็บไซต์", "Cache Base Model List": "แคชรายการโมเดลพื้นฐาน", "Calendar": "ปฏิทิน", + "Calendar deleted": "", "Calendars": "", "Call": "โทร", "Call feature is not supported when using Web STT engine": "ไม่รองรับฟีเจอร์การโทรเมื่อใช้เอนจิน Web STT", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "เชื่อมต่อกับเซิร์ฟเวอร์เครื่องมือภายนอกของคุณที่รองรับ OpenAPI", "Connected ({{type}})": "", "Connection failed": "การเชื่อมต่อล้มเหลว", + "Connection lost. Reconnecting...": "", "Connection successful": "เชื่อมต่อสำเร็จ", "Connection Type": "ประเภทการเชื่อมต่อ", "Connections": "การเชื่อมต่อ", @@ -524,6 +532,8 @@ "Delete All Chats": "ลบการแชททั้งหมด", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "ลบแชท", "Delete chat?": "ลบแชท?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "คัดลอกลิงก์ไม่สำเร็จ", "Failed to create API Key.": "สร้าง API Key ล้มเหลว", + "Failed to delete calendar": "", "Failed to delete note": "ลบบันทึกไม่สำเร็จ", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "ไม่สามารถดึงเนื้อหาจากไฟล์ได้: {{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "ระดับการใช้เหตุผล", "Reasoning Tags": "ป้ายกำกับการให้เหตุผล", "Recently Used": "", + "Reconnected": "", "Record": "บันทึก", "Record voice": "บันทึกเสียง", "Redirecting you to Open WebUI Community": "กำลังเปลี่ยนเส้นทางคุณไปยังชุมชน Open WebUI", @@ -1647,6 +1659,7 @@ "Relevance": "ความเกี่ยวข้อง", "Relevance Threshold": "เกณฑ์ความเกี่ยวข้อง", "Remember Dismissal": "จำการปิดข้อความ", + "Reminder": "", "Remove": "ลบ", "Remove {{MODELID}} from list.": "ลบ {{MODELID}} ออกจากรายการ", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "เริ่มการสนทนาใหม่", "Start of the channel": "จุดเริ่มต้นของช่อง", "Start Tag": "แท็กเริ่มต้น", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "การดำเนินการนี้จะลบ {{NAME}} และเนื้อหาทั้งหมด", "This will delete all models including custom models": "การดำเนินการนี้จะลบโมเดลทั้งหมด รวมถึงโมเดลแบบกำหนดเอง", "This will delete all models including custom models and cannot be undone.": "การดำเนินการนี้จะลบโมเดลทั้งหมดรวมถึงโมเดลที่กำหนดเอง และไม่สามารถยกเลิกได้", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "การดำเนินการนี้จะรีเซ็ตฐานความรู้และซิงค์ไฟล์ทั้งหมด คุณต้องการดำเนินการต่อหรือไม่?", "Thorough explanation": "คำอธิบายอย่างละเอียด", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "ยกเลิกการใช้งาน {{FROM_NOW}}", "Unlock mysteries": "ไขปริศนา", "Unpin": "ยกเลิกการปักหมุด", + "Unpin from Sidebar": "", "Unravel secrets": "เปิดเผยความลับ", "Unshare Chat": "", "Unsupported file type.": "ไม่รองรับไฟล์ประเภทนี้", diff --git a/src/lib/i18n/locales/tk-TM/translation.json b/src/lib/i18n/locales/tk-TM/translation.json index 7fb04a3227..6783a0222a 100644 --- a/src/lib/i18n/locales/tk-TM/translation.json +++ b/src/lib/i18n/locales/tk-TM/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}'iň Çatlary", "{{webUIName}} Backend Required": "{{webUIName}} Backend Zerur", "*Prompt node ID(s) are required for image generation": "", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "", @@ -202,6 +207,7 @@ "Ask a question": "", "Assistant": "", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "", "Call feature is not supported when using Web STT engine": "", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "Baglanyşyklar", @@ -525,6 +533,8 @@ "Delete All Chats": "Ähli Çatlary Öçür", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "", "Delete chat?": "", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "", "Redirecting you to Open WebUI Community": "", @@ -1648,6 +1660,7 @@ "Relevance": "", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Aýyr", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Kanal başy", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "", "Thorough explanation": "", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index 8d9e63ce85..5b31954239 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}}'ın Sohbetleri", "{{webUIName}} Backend Required": "{{webUIName}} Arka-uç Gerekli", "*Prompt node ID(s) are required for image generation": "*Görüntü oluşturma için düğüm ID'leri gereklidir", + "1 hour before": "", "1 Source": "1 Kaynak", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "1 dk önce", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "İnsanların üye olarak katıldığı bir iş birliği kanalı", "A discussion channel where access is controlled by groups and permissions": "Erişimin gruplar ve izinlerle kontrol edildiği bir tartışma kanalı", "A new version (v{{LATEST_VERSION}}) is now available.": "Yeni bir sürüm (v{{LATEST_VERSION}}) artık mevcut.", @@ -202,6 +207,7 @@ "Ask a question": "Bir soru sorun", "Assistant": "Asistan", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "Bilgi Tabanından Dosya Ekle", "Attach Files": "", "Attach Knowledge": "Bilgi Tabanı Ekle", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Web Yükleyicisini Atla", "Cache Base Model List": "Temel Model Listesini Önbelleğe Al", "Calendar": "Takvim", + "Calendar deleted": "", "Calendars": "", "Call": "Arama", "Call feature is not supported when using Web STT engine": "Web STT motoru kullanılırken arama özelliği desteklenmiyor", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Kendi OpenAPI uyumlu harici araç sunucularınıza bağlanın.", "Connected ({{type}})": "", "Connection failed": "Bağlantı başarısız", + "Connection lost. Reconnecting...": "", "Connection successful": "Bağlantı başarılı", "Connection Type": "Bağlantı Tipi", "Connections": "Bağlantılar", @@ -525,6 +533,8 @@ "Delete All Chats": "Tüm Sohbetleri Sil", "Delete all contents inside this folder": "Bu klasördeki tüm içerikleri sil", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Sohbeti Sil", "Delete chat?": "Sohbeti sil?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "{{URL}} terminal sunucusuna bağlanılamadı", "Failed to copy link": "Bağlantı kopyalanamadı", "Failed to create API Key.": "API Anahtarı oluşturulamadı.", + "Failed to delete calendar": "", "Failed to delete note": "Not silinemedi", "Failed to download image": "Görsel indirilemedi", "Failed to extract content from the file: {{error}}": "Dosyadan içerik çıkarılamadı: {{error}}", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Kaydet", "Record voice": "Ses kaydı yap", "Redirecting you to Open WebUI Community": "OpenWebUI Topluluğuna yönlendiriliyorsunuz", @@ -1648,6 +1660,7 @@ "Relevance": "İlgili", "Relevance Threshold": "İlgi Eşiği", "Remember Dismissal": "", + "Reminder": "", "Remove": "Kaldır", "Remove {{MODELID}} from list.": "{{MODELID}} modelini listeden kaldır.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "Yeni bir konuşma başlat", "Start of the channel": "Kanalın başlangıcı", "Start Tag": "Başlangıç Etiketi", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "Kernel başlatılıyor...", + "Starting now": "", "State": "", "Status": "Durum", "Status cleared successfully": "Durum başarıyla temizlendi", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "{{NAME}} ve tüm içeriği silinecek.", "This will delete all models including custom models": "Bu, özel modeller dahil olmak üzere tüm modelleri silecek", "This will delete all models including custom models and cannot be undone.": "Bu, özel modeller dahil olmak üzere tüm modelleri silecek ve geri alınamaz.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Bu, bilgi tabanını sıfırlayacak ve tüm dosyaları senkronize edecek. Devam etmek istiyor musunuz?", "Thorough explanation": "Kapsamlı açıklama", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} sonra modeli bellekten boşaltır", "Unlock mysteries": "", "Unpin": "Sabitlemeyi Kaldır", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ug-CN/translation.json b/src/lib/i18n/locales/ug-CN/translation.json index ea8c92b507..ba0727be45 100644 --- a/src/lib/i18n/locales/ug-CN/translation.json +++ b/src/lib/i18n/locales/ug-CN/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} نىڭ سۆھبەتلىرى", "{{webUIName}} Backend Required": "{{webUIName}} ئارقا سۇپا زۆرۈر", "*Prompt node ID(s) are required for image generation": "رەسىم ھاسىل قىلىش ئۈچۈن تۈرتكە نۇسخا ئۇچۇر ID(لىرى) زۆرۈر", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "يېڭى نەشرى (v{{LATEST_VERSION}}) مەۋجۇت.", @@ -202,6 +207,7 @@ "Ask a question": "سؤئال سوراڭ", "Assistant": "ياردەمچى", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "تور يۈكلىگۈچتىن ئۆتۈپ كېتىش", "Cache Base Model List": "", "Calendar": "كالىندار", + "Calendar deleted": "", "Calendars": "", "Call": "چاقىرىش", "Call feature is not supported when using Web STT engine": "تور STT ماتورى ئىشلىتىلگەندە چاقىرىش ئىقتىدارى قوللىنىلمايدۇ", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "OpenAPI ماس كېلىدىغان سىرتقى قورال مۇلازىمېتىرلىرىغا باغلىنىڭ.", "Connected ({{type}})": "", "Connection failed": "ئۇلىنىش مەغلۇپ بولدى", + "Connection lost. Reconnecting...": "", "Connection successful": "ئۇلىنىش مۇۋەپپەقىيەتلىك", "Connection Type": "ئۇلىنىش تىپى", "Connections": "ئۇلىنىشلەر", @@ -525,6 +533,8 @@ "Delete All Chats": "بارلىق سۆھبەتلەرنى ئۆچۈرۈش", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "سۆھبەت ئۆچۈرۈش", "Delete chat?": "سۆھبەت ئۆچۈرەمسىز؟", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "ئۇلانما كۆچۈرۈش مەغلۇپ بولدى", "Failed to create API Key.": "API ئاچقۇچى قۇرۇش مەغلۇپ بولدى.", + "Failed to delete calendar": "", "Failed to delete note": "خاتىرە ئۆچۈرۈش مەغلۇپ بولدى", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "چۈشەندۈرۈش كۈچى", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "خاتىرىلەش", "Record voice": "ئاۋاز خاتىرىلەش", "Redirecting you to Open WebUI Community": "Open WebUI جەمئىيىتىگە يوللاندى", @@ -1648,6 +1660,7 @@ "Relevance": "مۇناسىۋەتلىكلىك", "Relevance Threshold": "مۇناسىۋەتلىكلىك چەك قىممىتى", "Remember Dismissal": "", + "Reminder": "", "Remove": "چىقىرىۋېتىش", "Remove {{MODELID}} from list.": "تىزىمدىن {{MODELID}} چىقىرىۋېتىش.", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "قانالنىڭ باشلانغىنى", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "{{NAME}} ۋە بارلىق مەزمۇنى ئۆچۈرۈلىدۇ.", "This will delete all models including custom models": "بۇ بارلىق مودېللارنى ئۆچۈرۈدۇ (ئۆزلۈك مودېللارنىمۇ ئۆز ئىچىگە ئالىدۇ)", "This will delete all models including custom models and cannot be undone.": "بۇ بارلىق مودېللارنى ئۆچۈرۈدۇ (ئۆزلۈك مودېللارنىمۇ ئۆز ئىچىگە ئالىدۇ) ۋە ئەسلىگە كەلتۈرگىلى بولمايدۇ.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "بىلىم ئاساسى قايتا تەڭشىلىپ بارلىق ھۆججەتلەر ماس-قەدەملىنىدۇ. داۋاملاشامسىز؟", "Thorough explanation": "تەپسىلىي چۈشەندۈرۈش", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} چىقىرىلىدۇ", "Unlock mysteries": "سىرلارنى ئاچ", "Unpin": "مۇقىملانمىغان قىلىش", + "Unpin from Sidebar": "", "Unravel secrets": "سىرنى ئاچ", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index 5dde246b1f..46de023a39 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -35,8 +35,13 @@ "{{user}}'s Chats": "Чати {{user}}а", "{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}", "*Prompt node ID(s) are required for image generation": "*Для генерації зображення потрібно вказати ідентифікатор(и) вузла(ів)", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Нова версія (v{{LATEST_VERSION}}) зараз доступна.", @@ -204,6 +209,7 @@ "Ask a question": "Задати питання", "Assistant": "Асистент", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -278,6 +284,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Календар", + "Calendar deleted": "", "Calendars": "", "Call": "Виклик", "Call feature is not supported when using Web STT engine": "Функція виклику не підтримується при використанні Web STT (розпізнавання мовлення) рушія", @@ -415,6 +422,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Підключіться до своїх власних зовнішніх серверів інструментів, сумісних з OpenAPI.", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "З'єднання", @@ -527,6 +535,8 @@ "Delete All Chats": "Видалити усі чати", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Видалити чат", "Delete chat?": "Видалити чат?", "Delete Event": "", @@ -888,6 +898,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Не вдалося створити API ключ.", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1627,6 +1638,7 @@ "Reasoning Effort": "Зусилля на міркування", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Записати голос", "Redirecting you to Open WebUI Community": "Перенаправляємо вас до спільноти OpenWebUI", @@ -1650,6 +1662,7 @@ "Relevance": "Актуальність", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Видалити", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1898,7 +1911,13 @@ "Start a new conversation": "", "Start of the channel": "Початок каналу", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_few": "", + "Starting in {{count}} minutes_many": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2011,6 +2030,7 @@ "This will delete {{NAME}} and all its contents.": "Це видалить {{NAME}} та усі його вмісти.", "This will delete all models including custom models": "Це видалить усі моделі, включаючи користувацькі моделі", "This will delete all models including custom models and cannot be undone.": "Це видалить усі моделі, включаючи користувацькі моделі, і не може бути скасовано.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Це скине базу знань і синхронізує усі файли. Ви бажаєте продовжити?", "Thorough explanation": "Детальне пояснення", "Thought": "", @@ -2097,6 +2117,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Розкрийте таємниці", "Unpin": "Відчепити", + "Unpin from Sidebar": "", "Unravel secrets": "Розплутуйте секрети", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/ur-PK/translation.json b/src/lib/i18n/locales/ur-PK/translation.json index bc6d4912e0..967dd471db 100644 --- a/src/lib/i18n/locales/ur-PK/translation.json +++ b/src/lib/i18n/locales/ur-PK/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{ صارف }} کی بات چیت", "{{webUIName}} Backend Required": "{{webUIName}} بیک اینڈ درکار ہے", "*Prompt node ID(s) are required for image generation": "تصویر کی تخلیق کے لیے *پرومپٹ نوڈ آئی ڈی(ز) کی ضرورت ہے", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "نیا ورژن (v{{LATEST_VERSION}}) اب دستیاب ہے", @@ -202,6 +207,7 @@ "Ask a question": "سوال پوچھیں", "Assistant": "اسسٹنٹ", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "", + "Calendar deleted": "", "Calendars": "", "Call": "کال کریں", "Call feature is not supported when using Web STT engine": "کال کی خصوصیت ویب STT انجن استعمال کرتے وقت معاونت یافتہ نہیں ہے", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "", "Connected ({{type}})": "", "Connection failed": "", + "Connection lost. Reconnecting...": "", "Connection successful": "", "Connection Type": "", "Connections": "کنکشنز", @@ -525,6 +533,8 @@ "Delete All Chats": "تمام چیٹس حذف کریں", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "چیٹ حذف کریں", "Delete chat?": "چیٹ حذف کریں؟", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "API کلید بنانے میں ناکام", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "صوت ریکارڈ کریں", "Redirecting you to Open WebUI Community": "آپ کو اوپن ویب یو آئی کمیونٹی کی طرف ری ڈائریکٹ کیا جا رہا ہے", @@ -1648,6 +1660,7 @@ "Relevance": "موزونیت", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "ہٹا دیں", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "چینل کی شروعات", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "یہ {{NAME}} اور اس کے تمام مواد کو حذف کر دے گا", "This will delete all models including custom models": "", "This will delete all models including custom models and cannot be undone.": "", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "یہ علمی بنیاد کو دوبارہ ترتیب دے گا اور تمام فائلز کو متوازن کرے گا کیا آپ جاری رکھنا چاہتے ہیں؟", "Thorough explanation": "مکمل وضاحت", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "", "Unpin": "ان پن کریں", + "Unpin from Sidebar": "", "Unravel secrets": "", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json index b4193be7b8..fe4f5b1da1 100644 --- a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json +++ b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} нинг чатлари", "{{webUIName}} Backend Required": "{{webUIName}} Баcкенд талаб қилинади", "*Prompt node ID(s) are required for image generation": "*Расм яратиш учун тезкор тугун идентификаторлари талаб қилинади", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Энди янги версия (v{{LATEST_VERSION}}) мавжуд.", @@ -202,6 +207,7 @@ "Ask a question": "Савол беринг", "Assistant": "Ёрдамчи", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Веб юклагични четлаб ўтиш", "Cache Base Model List": "", "Calendar": "Календар", + "Calendar deleted": "", "Calendars": "", "Call": "Қўнғироқ қилинг", "Call feature is not supported when using Web STT engine": "Wеб СТТ механизмидан фойдаланилганда қўнғироқ функсияси қўллаб-қувватланмайди", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Ўзингизнинг OpenAIга мос келадиган ташқи асбоблар серверларига уланинг.", "Connected ({{type}})": "", "Connection failed": "Уланиш амалга ошмади", + "Connection lost. Reconnecting...": "", "Connection successful": "Уланиш муваффақиятли", "Connection Type": "Уланиш тури", "Connections": "Уланишлар", @@ -525,6 +533,8 @@ "Delete All Chats": "Барча суҳбатларни ўчириш", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Чатни ўчириш", "Delete chat?": "Чат ўчирилсинми?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Ҳаволани нусхалаб бўлмади", "Failed to create API Key.": "АПИ калитини яратиб бўлмади.", + "Failed to delete calendar": "", "Failed to delete note": "Қайдни ўчириб бўлмади", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Мулоҳаза юритиш ҳаракатлари", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Ёзиб олиш", "Record voice": "Овозни ёзиб олинг", "Redirecting you to Open WebUI Community": "Сизни Опен WебУИ ҳамжамиятига йўналтирмоқда", @@ -1648,6 +1660,7 @@ "Relevance": "Мувофиқлик", "Relevance Threshold": "Мувофиқлик чегараси", "Remember Dismissal": "", + "Reminder": "", "Remove": "Ўчириш", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Канал боши", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Бу <стронг>{{NAME}} ва <стронг>барча мазмунини ўчириб ташлайди.", "This will delete all models including custom models": "Бу барча моделларни, шу жумладан махсус моделларни ўчириб ташлайди", "This will delete all models including custom models and cannot be undone.": "Бу барча моделларни, жумладан, махсус моделларни ҳам ўчириб ташлайди ва уларни ортга қайтариб бўлмайди.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Бу билимлар базасини қайта тиклайди ва барча файлларни синхронлаштиради. Давом этишни хоҳлайсизми?", "Thorough explanation": "Тўлиқ тушунтириш", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} юклайди", "Unlock mysteries": "Сирларни очинг", "Unpin": "Ечиш", + "Unpin from Sidebar": "", "Unravel secrets": "Сирларни очинг", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/uz-Latn-Uz/translation.json b/src/lib/i18n/locales/uz-Latn-Uz/translation.json index b7c12ae135..2ffada0eab 100644 --- a/src/lib/i18n/locales/uz-Latn-Uz/translation.json +++ b/src/lib/i18n/locales/uz-Latn-Uz/translation.json @@ -33,8 +33,13 @@ "{{user}}'s Chats": "{{user}} ning chatlari", "{{webUIName}} Backend Required": "{{webUIName}} Backend talab qilinadi", "*Prompt node ID(s) are required for image generation": "*Rasm yaratish uchun tezkor tugun identifikatorlari talab qilinadi", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Endi yangi versiya (v{{LATEST_VERSION}}) mavjud.", @@ -202,6 +207,7 @@ "Ask a question": "Savol bering", "Assistant": "Yordamchi", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -276,6 +282,7 @@ "Bypass Web Loader": "Veb yuklagichni chetlab o'tish", "Cache Base Model List": "", "Calendar": "Kalendar", + "Calendar deleted": "", "Calendars": "", "Call": "Qo'ng'iroq qiling", "Call feature is not supported when using Web STT engine": "Web STT mexanizmidan foydalanilganda qo'ng'iroq funksiyasi qo'llab-quvvatlanmaydi", @@ -413,6 +420,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "O'zingizning OpenAPI-ga mos keladigan tashqi asboblar serverlariga ulaning.", "Connected ({{type}})": "", "Connection failed": "Ulanish amalga oshmadi", + "Connection lost. Reconnecting...": "", "Connection successful": "Ulanish muvaffaqiyatli", "Connection Type": "Ulanish turi", "Connections": "Ulanishlar", @@ -525,6 +533,8 @@ "Delete All Chats": "Barcha suhbatlarni o'chirish", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Chatni oʻchirish", "Delete chat?": "Chat oʻchirilsinmi?", "Delete Event": "", @@ -886,6 +896,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "Havolani nusxalab bo‘lmadi", "Failed to create API Key.": "API kalitini yaratib bo‘lmadi.", + "Failed to delete calendar": "", "Failed to delete note": "Qaydni o‘chirib bo‘lmadi", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1625,6 +1636,7 @@ "Reasoning Effort": "Mulohaza yuritish harakatlari", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "Yozib olish", "Record voice": "Ovozni yozib oling", "Redirecting you to Open WebUI Community": "Sizni Open WebUI hamjamiyatiga yoʻnaltirmoqda", @@ -1648,6 +1660,7 @@ "Relevance": "Muvofiqlik", "Relevance Threshold": "Muvofiqlik chegarasi", "Remember Dismissal": "", + "Reminder": "", "Remove": "O'chirish", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1894,7 +1907,11 @@ "Start a new conversation": "", "Start of the channel": "Kanal boshlanishi", "Start Tag": "", + "Starting in {{count}} minutes_one": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2007,6 +2024,7 @@ "This will delete {{NAME}} and all its contents.": "Bu {{NAME}} va barcha mazmunini o‘chirib tashlaydi.", "This will delete all models including custom models": "Bu barcha modellarni, shu jumladan maxsus modellarni o'chirib tashlaydi", "This will delete all models including custom models and cannot be undone.": "Bu barcha modellarni, jumladan, maxsus modellarni ham o‘chirib tashlaydi va ularni ortga qaytarib bo‘lmaydi.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Bu bilimlar bazasini qayta tiklaydi va barcha fayllarni sinxronlashtiradi. Davom etishni xohlaysizmi?", "Thorough explanation": "To'liq tushuntirish", "Thought": "", @@ -2093,6 +2111,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} yuklaydi", "Unlock mysteries": "Sirlarni oching", "Unpin": "Yechish", + "Unpin from Sidebar": "", "Unravel secrets": "Sirlarni oching", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 9296b8b57c..6810eb909a 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "Các cuộc trò chuyện của {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend", "*Prompt node ID(s) are required for image generation": "*ID nút Prompt là bắt buộc để tạo ảnh", + "1 hour before": "", "1 Source": "", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "", "A discussion channel where access is controlled by groups and permissions": "", "A new version (v{{LATEST_VERSION}}) is now available.": "Một phiên bản mới (v{{LATEST_VERSION}}) đã có sẵn.", @@ -201,6 +206,7 @@ "Ask a question": "Đặt câu hỏi", "Assistant": "Trợ lý", "Async Embedding Processing": "", + "At time of event": "", "Attach File From Knowledge": "", "Attach Files": "", "Attach Knowledge": "", @@ -275,6 +281,7 @@ "Bypass Web Loader": "", "Cache Base Model List": "", "Calendar": "Lịch", + "Calendar deleted": "", "Calendars": "", "Call": "Gọi", "Call feature is not supported when using Web STT engine": "Tính năng gọi điện không được hỗ trợ khi sử dụng công cụ Web STT", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "Kết nối với các máy chủ công cụ bên ngoài tương thích OpenAPI của riêng bạn.", "Connected ({{type}})": "", "Connection failed": "Kết nối thất bại", + "Connection lost. Reconnecting...": "", "Connection successful": "Kết nối thành công", "Connection Type": "", "Connections": "Kết nối", @@ -524,6 +532,8 @@ "Delete All Chats": "Xóa mọi cuộc Chat", "Delete all contents inside this folder": "", "Delete automation?": "", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "Xóa chat", "Delete chat?": "Xóa chat?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "", "Failed to copy link": "", "Failed to create API Key.": "Lỗi khởi tạo API Key", + "Failed to delete calendar": "", "Failed to delete note": "", "Failed to download image": "", "Failed to extract content from the file: {{error}}": "", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "Nỗ lực Suy luận", "Reasoning Tags": "", "Recently Used": "", + "Reconnected": "", "Record": "", "Record voice": "Ghi âm", "Redirecting you to Open WebUI Community": "Đang chuyển hướng bạn đến Cộng đồng OpenWebUI", @@ -1647,6 +1659,7 @@ "Relevance": "Mức độ liên quan", "Relevance Threshold": "", "Remember Dismissal": "", + "Reminder": "", "Remove": "Xóa", "Remove {{MODELID}} from list.": "", "Remove action": "", @@ -1892,7 +1905,10 @@ "Start a new conversation": "", "Start of the channel": "Đầu kênh", "Start Tag": "", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "", + "Starting now": "", "State": "", "Status": "", "Status cleared successfully": "", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "Hành động này sẽ xóa {{NAME}}tất cả nội dung của nó.", "This will delete all models including custom models": "Hành động này sẽ xóa tất cả các mô hình bao gồm cả các mô hình tùy chỉnh", "This will delete all models including custom models and cannot be undone.": "Hành động này sẽ xóa tất cả các mô hình bao gồm cả các mô hình tùy chỉnh và không thể hoàn tác.", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Hành động này sẽ đặt lại cơ sở kiến thức và đồng bộ hóa tất cả các tệp. Bạn có muốn tiếp tục không?", "Thorough explanation": "Giải thích kỹ lưỡng", "Thought": "", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "", "Unlock mysteries": "Mở khóa những bí ẩn", "Unpin": "Bỏ ghim", + "Unpin from Sidebar": "", "Unravel secrets": "Làm sáng tỏ những bí mật", "Unshare Chat": "", "Unsupported file type.": "", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index c8e04258c2..ce09ad948c 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "{{user}} 的对话记录", "{{webUIName}} Backend Required": "{{webUIName}} 需要后端服务", "*Prompt node ID(s) are required for image generation": "*图片生成需要提示词节点 ID", + "1 hour before": "", "1 Source": "1 个引用来源", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "刚刚", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "成员可加入的协作频道", "A discussion channel where access is controlled by groups and permissions": "由用户组控制的讨论频道", "A new version (v{{LATEST_VERSION}}) is now available.": "新版本(v{{LATEST_VERSION}})现已发布", @@ -201,6 +206,7 @@ "Ask a question": "提问", "Assistant": "助手", "Async Embedding Processing": "异步嵌入处理", + "At time of event": "", "Attach File From Knowledge": "引用知识库中的文件", "Attach Files": "添加文件", "Attach Knowledge": "引用知识库", @@ -275,6 +281,7 @@ "Bypass Web Loader": "绕过网页加载器", "Cache Base Model List": "缓存基础模型列表", "Calendar": "日历", + "Calendar deleted": "", "Calendars": "", "Call": "语音通话", "Call feature is not supported when using Web STT engine": "使用 Web 语音转文字引擎时不支持语音通话功能", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "连接到符合 OpenAPI 规范的外部工具服务器", "Connected ({{type}})": "已连接({{type}})", "Connection failed": "连接失败", + "Connection lost. Reconnecting...": "", "Connection successful": "连接成功", "Connection Type": "连接类型", "Connections": "外部连接", @@ -524,6 +532,8 @@ "Delete All Chats": "删除所有对话记录", "Delete all contents inside this folder": "删除此分组内的所有内容", "Delete automation?": "要删除此自动化吗?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "删除对话记录", "Delete chat?": "要删除此对话记录吗?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "无法连接到终端服务器:{{URL}}", "Failed to copy link": "复制链接失败", "Failed to create API Key.": "创建接口密钥失败", + "Failed to delete calendar": "", "Failed to delete note": "删除笔记失败", "Failed to download image": "图片下载失败", "Failed to extract content from the file: {{error}}": "文件内容提取失败:{{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "推理努力 (Reasoning Effort)", "Reasoning Tags": "推理过程标签", "Recently Used": "最近使用", + "Reconnected": "", "Record": "录制", "Record voice": "录音", "Redirecting you to Open WebUI Community": "正在将您重定向到 Open WebUI 社区", @@ -1647,6 +1659,7 @@ "Relevance": "相关性", "Relevance Threshold": "相关性阈值", "Remember Dismissal": "记住关闭状态", + "Reminder": "", "Remove": "移除", "Remove {{MODELID}} from list.": "从列表中移除 {{MODELID}}", "Remove action": "删除当前操作", @@ -1892,7 +1905,10 @@ "Start a new conversation": "开始新对话", "Start of the channel": "频道起点", "Start Tag": "起始标签", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "正在启动内核...", + "Starting now": "", "State": "状态", "Status": "状态", "Status cleared successfully": "状态已清除", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "这将删除{{NAME}}及其所有内容。", "This will delete all models including custom models": "这将删除所有模型,包括自定义模型", "This will delete all models including custom models and cannot be undone.": "这将删除所有模型,包括自定义模型,且无法撤销。", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "这将重置知识库并同步所有文件。确认继续?", "Thorough explanation": "解释详尽", "Thought": "思考过程", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "{{FROM_NOW}} 后卸载", "Unlock mysteries": "解码未知", "Unpin": "取消置顶", + "Unpin from Sidebar": "", "Unravel secrets": "冲破奥秘", "Unshare Chat": "取消分享对话", "Unsupported file type.": "不支持的文件类型", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index f98a2bdb76..50d352a96f 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -32,8 +32,13 @@ "{{user}}'s Chats": "{{user}} 的對話", "{{webUIName}} Backend Required": "需要提供 {{webUIName}} 後端", "*Prompt node ID(s) are required for image generation": "* 產生圖片需要提示詞節點 ID", + "1 hour before": "", "1 Source": "1 個來源", + "10 minutes before": "", + "15 minutes before": "", "1m_time_ago": "剛剛", + "30 minutes before": "", + "5 minutes before": "", "A collaboration channel where people join as members": "成員可加入的協作頻道", "A discussion channel where access is controlled by groups and permissions": "由權限組控制的討論頻道", "A new version (v{{LATEST_VERSION}}) is now available.": "新版本 (v{{LATEST_VERSION}}) 已釋出。", @@ -201,6 +206,7 @@ "Ask a question": "提出問題", "Assistant": "助理", "Async Embedding Processing": "非同步嵌入處理", + "At time of event": "", "Attach File From Knowledge": "從知識庫附加檔案", "Attach Files": "新增檔案", "Attach Knowledge": "附加知識庫", @@ -275,6 +281,7 @@ "Bypass Web Loader": "繞過網頁載入器", "Cache Base Model List": "快取基礎模型清單", "Calendar": "日曆", + "Calendar deleted": "", "Calendars": "", "Call": "通話", "Call feature is not supported when using Web STT engine": "使用網頁語音辨識 (Web STT) 引擎時不支援通話功能", @@ -412,6 +419,7 @@ "Connect to your own OpenAPI compatible external tool servers.": "連線至您自有或其他與 OpenAPI 相容的外部工具伺服器。", "Connected ({{type}})": "已連線({{type}})", "Connection failed": "連線失敗", + "Connection lost. Reconnecting...": "", "Connection successful": "連線成功", "Connection Type": "連線類型", "Connections": "連線", @@ -524,6 +532,8 @@ "Delete All Chats": "刪除所有對話紀錄", "Delete all contents inside this folder": "刪除此資料夾內的所有內容", "Delete automation?": "要刪除此自動化嗎?", + "Delete calendar": "", + "Delete Calendar": "", "Delete Chat": "刪除對話紀錄", "Delete chat?": "刪除對話紀錄?", "Delete Event": "", @@ -885,6 +895,7 @@ "Failed to connect to {{URL}} terminal server": "無法連線至終端伺服器:{{URL}}", "Failed to copy link": "複製連結失敗", "Failed to create API Key.": "建立 API 金鑰失敗。", + "Failed to delete calendar": "", "Failed to delete note": "刪除筆記失敗", "Failed to download image": "圖片下載失敗", "Failed to extract content from the file: {{error}}": "檔案內容擷取失敗:{{error}}", @@ -1624,6 +1635,7 @@ "Reasoning Effort": "推理程度", "Reasoning Tags": "推理標籤", "Recently Used": "最近使用", + "Reconnected": "", "Record": "錄製", "Record voice": "錄音", "Redirecting you to Open WebUI Community": "正在將您重導向至 Open WebUI 社群", @@ -1647,6 +1659,7 @@ "Relevance": "相關性", "Relevance Threshold": "相關性閾值", "Remember Dismissal": "記住關閉狀態", + "Reminder": "", "Remove": "移除", "Remove {{MODELID}} from list.": "從清單中移除 {{MODELID}}", "Remove action": "刪除目前操作", @@ -1892,7 +1905,10 @@ "Start a new conversation": "開始新對話", "Start of the channel": "頻道起點", "Start Tag": "起始標籤", + "Starting in {{count}} minutes_other": "", + "Starting in 1 minute": "", "Starting kernel...": "正在啟動核心…", + "Starting now": "", "State": "狀態", "Status": "狀態", "Status cleared successfully": "狀態已清除", @@ -2005,6 +2021,7 @@ "This will delete {{NAME}} and all its contents.": "這將會刪除 {{NAME}}其所有內容。", "This will delete all models including custom models": "這將刪除所有模型,包括自訂模型", "This will delete all models including custom models and cannot be undone.": "這將刪除所有模型,包括自訂模型,且無法復原。", + "This will permanently delete the calendar \"{{name}}\" and all its events. This action cannot be undone.": "", "This will reset the knowledge base and sync all files. Do you wish to continue?": "這將重設知識庫並同步所有檔案。您確定要繼續嗎?", "Thorough explanation": "詳細解釋", "Thought": "思考過程", @@ -2091,6 +2108,7 @@ "Unloads {{FROM_NOW}}": "於 {{FROM_NOW}} 後解除載入", "Unlock mysteries": "解鎖謎題", "Unpin": "取消釘選", + "Unpin from Sidebar": "", "Unravel secrets": "揭開秘密", "Unshare Chat": "取消分享對話", "Unsupported file type.": "不支援的檔案類型", From 0542df147a90565cfec224af46e589200ddd9553 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 15:52:33 +0900 Subject: [PATCH 111/119] refac --- src/lib/components/chat/ChatControls.svelte | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte index 3cbcc7ed87..8ac5f06617 100644 --- a/src/lib/components/chat/ChatControls.svelte +++ b/src/lib/components/chat/ChatControls.svelte @@ -72,7 +72,10 @@ $: showControlsTab = $user?.role === 'admin' || ($user?.permissions?.chat?.controls ?? true); $: showFilesTab = - !!$selectedTerminalId || + ($selectedTerminalId && + (($terminalServers ?? []).some((t) => t.id && t.id === $selectedTerminalId) || + $user?.role === 'admin' || + ($user?.permissions?.features?.direct_tool_servers ?? true))) || (codeInterpreterEnabled && $config?.code?.interpreter_engine !== 'jupyter'); $: showOverviewTab = hasMessages; @@ -96,13 +99,22 @@ } // Auto-open Files tab when a terminal is selected (suppress panel open when full-screen) - $: if ($selectedTerminalId) { + $: if ($selectedTerminalId && showFilesTab) { activeTab = 'files'; if (largeScreen) { showControls.set(true); } } + // Clear selected direct terminal if user lost permission + $: if ( + $selectedTerminalId && + !($terminalServers ?? []).some((t) => t.id && t.id === $selectedTerminalId) && + !($user?.role === 'admin' || ($user?.permissions?.features?.direct_tool_servers ?? true)) + ) { + selectedTerminalId.set(null); + } + // Attach a terminal file to the chat input const handleTerminalAttach = async (blob: Blob, name: string, contentType: string) => { const tempItemId = uuidv4(); From 65f55847a144a1c61775c0097116932f716ee7fa Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 16:04:48 +0900 Subject: [PATCH 112/119] refac --- backend/open_webui/retrieval/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index b9bfcc12c8..fb5a46c2b0 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -172,6 +172,11 @@ def _is_text_content_type(content_type: str) -> bool: def get_content_from_url(request, url: str) -> str: + from open_webui.retrieval.web.utils import validate_url + + # Validate URL before making any request (blocks private IPs, non-HTTP, filter list) + validate_url(url) + # Streamed GET to check Content-Type without downloading the body. try: response = requests.get(url, stream=True, timeout=30) From 116eb7fc5501e43d217489776d11792e4d2fe2ef Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 16:05:26 +0900 Subject: [PATCH 113/119] refac --- backend/open_webui/utils/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 9a35e30c3f..47302e7535 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -1922,10 +1922,10 @@ class OAuthManager: users_to_logout.append(user) if not users_to_logout and sid: - log.info(f'Back-channel logout: no user found by sub, sid-based lookup not yet supported (sid={sid})') + log.debug(f'Back-channel logout: no user found by sub, sid-based lookup not yet supported (sid={sid})') if not users_to_logout: - log.info(f'Back-channel logout: no matching user for provider={matched_provider}, sub={sub}, sid={sid}') + log.debug(f'Back-channel logout: no matching user for provider={matched_provider}, sub={sub}, sid={sid}') return JSONResponse(status_code=200, content={}) # 9. Revoke tokens and delete sessions From 085d3cb1c9e5046e0fa165e153bf821f53b795b9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 16:16:48 +0900 Subject: [PATCH 114/119] refac --- src/lib/utils/index.ts | 61 +++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index dc23620a12..38e7636e25 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -677,7 +677,9 @@ export const calculateSHA256 = async (file) => { export const getImportOrigin = (_chats) => { // Check what external service chat imports are from - if ('mapping' in _chats[0]) { + // ChatGPT exports may include folder/project metadata entries without 'mapping', + // so we check if ANY item has a 'mapping' key instead of only the first one. + if (_chats.some((chat) => 'mapping' in chat)) { return 'openai'; } return 'webui'; @@ -706,6 +708,21 @@ export const getUserPosition = async (raw = false) => { } }; +const extractOpenAIMessageContent = (message): string => { + // Extract text content from a ChatGPT message, handling various content formats + // (string parts, object parts like DALL-E images, text field fallback) + try { + const parts = message?.['content']?.['parts']; + if (Array.isArray(parts)) { + const textParts = parts.filter((p) => typeof p === 'string'); + if (textParts.length > 0) return textParts.join('\n'); + } + return message?.['content']?.['text'] || ''; + } catch { + return ''; + } +}; + const convertOpenAIMessages = (convo) => { // Parse OpenAI chat messages and create chat dictionary for creating new chats const mapping = convo['mapping']; @@ -726,15 +743,18 @@ const convertOpenAIMessages = (convo) => { // Skip chat messages with no content continue; } else { + const role = message['message']?.['author']?.['role']; + // Skip system and tool messages — they don't map to user/assistant + if (role === 'system' || role === 'tool') { + continue; + } + const new_chat = { id: message_id, parentId: lastId, childrenIds: message['children'] || [], - role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user', - content: - message['message']?.['content']?.['parts']?.[0] || - message['message']?.['content']?.['text'] || - '', + role: role !== 'user' ? 'assistant' : 'user', + content: extractOpenAIMessageContent(message['message']), model: 'gpt-3.5-turbo', done: true, context: null @@ -747,6 +767,12 @@ const convertOpenAIMessages = (convo) => { } } + // Fix up the last message's childrenIds to be empty (it's the leaf node in our + // linear chain regardless of what the original tree structure had) + if (messages.length > 0) { + messages[messages.length - 1].childrenIds = []; + } + const history: Record = {}; messages.forEach((obj) => (history[obj.id] = obj)); @@ -773,18 +799,6 @@ const validateChat = (chat) => { return false; } - // Last message's children should be an empty array - const lastMessage = messages[messages.length - 1]; - if (lastMessage.childrenIds.length !== 0) { - return false; - } - - // First message's parent should be null - const firstMessage = messages[0]; - if (firstMessage.parentId !== null) { - return false; - } - // Every message's content should be a string for (const message of messages) { if (typeof message.content !== 'string') { @@ -799,7 +813,15 @@ export const convertOpenAIChats = (_chats) => { // Create a list of dictionaries with each conversation from import const chats = []; let failed = 0; + let skipped = 0; for (const convo of _chats) { + // Skip folder/project metadata entries that lack a 'mapping' key + if (!('mapping' in convo)) { + skipped++; + console.log('Skipping non-conversation entry (folder/project):', convo['title'] ?? convo['id']); + continue; + } + const chat = convertOpenAIMessages(convo); if (validateChat(chat)) { @@ -815,6 +837,9 @@ export const convertOpenAIChats = (_chats) => { } } console.log(failed, 'Conversations could not be imported'); + if (skipped > 0) { + console.log(skipped, 'Non-conversation entries (folders/projects) were skipped'); + } return chats; }; From 3b821e1f3a54d56fcde9ee88ca977c8a0ff3caea Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 16:32:17 +0900 Subject: [PATCH 115/119] refac --- src/lib/utils/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 38e7636e25..1820e70481 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -818,7 +818,10 @@ export const convertOpenAIChats = (_chats) => { // Skip folder/project metadata entries that lack a 'mapping' key if (!('mapping' in convo)) { skipped++; - console.log('Skipping non-conversation entry (folder/project):', convo['title'] ?? convo['id']); + console.log( + 'Skipping non-conversation entry (folder/project):', + convo['title'] ?? convo['id'] + ); continue; } From 493f238431e5b06e488cb5f9607bab7465301300 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 21 Apr 2026 16:46:02 +0900 Subject: [PATCH 116/119] refac --- CHANGELOG.md | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5f34d74f..4e0b35b16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,26 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- 🖥️ **Native desktop app availability.** Open WebUI is now available as a cross-platform desktop app with local model support, multi-server switching, and offline-ready usage after first launch. [#8262](https://github.com/open-webui/open-webui/issues/8262), [Desktop](https://github.com/open-webui/desktop) -- 🤖 **Scheduled chat automations.** Users can now create, schedule, run, and manage recurring automations from both the dedicated automations page and built-in chat tools, with execution logs, direct run controls, and permission-aware access control for user and group policies. [#23303](https://github.com/open-webui/open-webui/pull/23303), [Commit](https://github.com/open-webui/open-webui/commit/5a2ff8b2e5b6f55a20f7ed491f818490eb535ea7), [Commit](https://github.com/open-webui/open-webui/commit/d30a0531d4add045c21a2368d6321a9b1906865f), [Commit](https://github.com/open-webui/open-webui/commit/bae5ff938ac88a3a647cc31ca8db1101015ae18b), [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) +- 🖥️ **Official Open WebUI Desktop App.** Open WebUI is now available as a native desktop app for Mac, Windows, and Linux. No Docker, no terminal, no setup. Runs Open WebUI locally without any server setup, or connects to your existing remote Open WebUI instances. Switch between multiple servers instantly from the sidebar. Comes with a system-wide floating chat bar (Shift+Cmd+I on macOS, Shift+Ctrl+I on Windows/Linux), system-wide push-to-talk, offline support after first launch, automatic updates, and zero telemetry. [#8262](https://github.com/open-webui/open-webui/issues/8262), [Desktop](https://github.com/open-webui/desktop) +- 🤖 **Scheduled chat automations.** You can now schedule the AI to run tasks automatically on a recurring basis: daily digests, periodic reports, anything you'd otherwise need to remember to ask for. Create and manage automations from the Automations page or directly in chat, with full run history and manual trigger controls. [#23303](https://github.com/open-webui/open-webui/pull/23303), [Commit](https://github.com/open-webui/open-webui/commit/5a2ff8b2e5b6f55a20f7ed491f818490eb535ea7), [Commit](https://github.com/open-webui/open-webui/commit/d30a0531d4add045c21a2368d6321a9b1906865f), [Commit](https://github.com/open-webui/open-webui/commit/bae5ff938ac88a3a647cc31ca8db1101015ae18b), [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) - 🧰 **Automation tools in chat.** Built-in chat tools can now create, update, list, pause, and delete scheduled automations directly in conversation when automation access is enabled. [Commit](https://github.com/open-webui/open-webui/commit/588b81eedaacbfd7394b707ae1600d9fb729b809..674695918e5e3e1811314ce2a082c5bbb42d76b2) -- 🤖 **Automation model selection reliability.** Automations created from chat now consistently use the calling model, avoiding mismatches when tool calls run under different model contexts. [Commit](https://github.com/open-webui/open-webui/commit/e709d6812f7fba246c4b7907f9fa41f751717566), [Commit](https://github.com/open-webui/open-webui/commit/398718d5059ce2a5614e9e124f20ef48b843ce42), [#23812](https://github.com/open-webui/open-webui/pull/23812) - ⏱️ **Automation scheduling limits.** Administrators can now set "AUTOMATION_MAX_COUNT" and "AUTOMATION_MIN_INTERVAL" to limit how many automations each non-admin user can create and prevent overly frequent schedules that could overload the system. [Commit](https://github.com/open-webui/open-webui/commit/406251c2f358ffabce4d631c98c6f2c879feae5c) -- 🧭 **Global automations toggle.** Administrators can now disable automations system-wide with the "ENABLE_AUTOMATIONS" setting, which hides automation pages and tools and pauses background automation processing until it is re-enabled. [Commit](https://github.com/open-webui/open-webui/commit/42694c7c0cc8ba586c1dd364ecfaa0b4080b6cad) - 📋 **Task management tool.** AI models can now create, update, and track tasks within a chat conversation, breaking down complex requests into manageable steps with real-time status updates. [Commit](https://github.com/open-webui/open-webui/commit/bcb71bb5206ac01d97a39fde8ecf0e0541dde636) -- 🗓️ **Calendar workspace and event management.** Users can now manage personal and shared calendars from a dedicated Calendar page, create and edit events (including recurring events), and view scheduled automations directly alongside calendar activity. [#23880](https://github.com/open-webui/open-webui/pull/23880) -- 🔐 **Calendar permission controls.** Administrators can now control calendar access through feature permissions, so calendar pages, APIs, and built-in calendar tools are available only to users and groups with calendar access enabled. [Commit](https://github.com/open-webui/open-webui/commit/5afc258c5b13f456be528420513ade546c5e86f9), [Commit](https://github.com/open-webui/open-webui/commit/37eba1c5a66b3145c122a6b40e5c29707526d121) -- 🗑️ **Calendar deletion controls.** Calendar sidebar entries now include a delete action with confirmation, allowing users to remove custom calendars directly from the Calendar page. [Commit](https://github.com/open-webui/open-webui/commit/51627555bf356c8ec663f4d2f43f2f013eadbce4) +- 🗓️ **Calendar workspace and event management.** Open WebUI now has a full Calendar workspace. Create and manage events, set up recurring schedules, get reminders via in-app toasts or browser notifications, and see your scheduled automations alongside your calendar. [#23880](https://github.com/open-webui/open-webui/pull/23880) - 🔔 **Calendar reminders and alerts.** Calendar events now support reminder options from no alert up to one hour before start time, with upcoming alerts delivered through in-app toasts, browser notifications, and optional webhooks while avoiding duplicate sends. [Commit](https://github.com/open-webui/open-webui/commit/e5b5a174265d6710e986f6534ee7e3b2923233be) - ⚙️ **Scheduler reminder configuration.** Administrators can now configure calendar reminder processing with "SCHEDULER_POLL_INTERVAL" and "CALENDAR_ALERT_LOOKAHEAD_MINUTES", while existing "AUTOMATION_POLL_INTERVAL" setups continue to work as a legacy fallback. [Commit](https://github.com/open-webui/open-webui/commit/e5b5a174265d6710e986f6534ee7e3b2923233be) -- 🗓️ **Unified calendar header controls.** The Calendar page now uses a single top navigation bar for date navigation, view selection, and quick event creation, with improved mobile behavior and label truncation for tighter screens. [Commit](https://github.com/open-webui/open-webui/commit/4e31fa4427037c0ffd4ad704308203639bf05df8), [Commit](https://github.com/open-webui/open-webui/commit/3e3f138d9323987a41b1e3c17721a0047cf8e40f) -- 🧰 **Dedicated task checklist tools.** Built-in task tracking exposes separate tools for creating task lists and updating individual task statuses, giving multi-step chats clearer progress control. [Commit](https://github.com/open-webui/open-webui/commit/a35926261646f8897ba71da1572ed5dff802e3be) - ☁️ **Azure responses support.** Azure OpenAI connections now support the newer "/openai/v1" format, enabling chat, responses, and proxy calls to work correctly with that endpoint style. [#23484](https://github.com/open-webui/open-webui/pull/23484) - 🤖 **Ollama responses support.** The Ollama proxy now supports the Responses API, letting clients use "/v1/responses" directly with Ollama-hosted models through Open WebUI. [#23483](https://github.com/open-webui/open-webui/pull/23483) - 🧩 **Responses tool output rendering.** Built-in tool outputs in Responses API flows now render more consistently so downstream chat output is easier to interpret. [Commit](https://github.com/open-webui/open-webui/commit/e695d854f2d11fada84d5fbec8d3edea4e468e19), [#23482](https://github.com/open-webui/open-webui/pull/23482) - 🔎 **Responses citation visibility.** Responses API flows now emit citation sources more consistently, making linked references easier to preserve and display in chat output. [Commit](https://github.com/open-webui/open-webui/commit/e695d854f2d11fada84d5fbec8d3edea4e468e19), [#23774](https://github.com/open-webui/open-webui/issues/23774) - 📎 **Attach previously uploaded files.** The chat input menu now includes a Files tab for browsing and attaching previously uploaded files, eliminating the need to re-upload files you have already shared. [Commit](https://github.com/open-webui/open-webui/commit/edb8971c7dbd974322c3207c4655ff66479c3ee2) -- 🖥️ **Terminal session tracking.** Open Terminal now tracks the current working directory per chat session, so relative paths and navigation work correctly across multiple interactions. [Commit](https://github.com/open-webui/open-webui/commit/a06685a47b89fb19dd6124fbe391ff78b54f451d), [Commit](https://github.com/open-webui/open-webui/commit/6512e085c4e56897dd49e56aff5d616820a962f3) - 🧷 **Default model terminal selection.** Workspace model editors can now preselect an Open Terminal connection, so new chats automatically start with the model’s configured terminal ready to use. [Commit](https://github.com/open-webui/open-webui/commit/47d413ce7b2a006a8126f4a9055b13e5fcb33a1d), [#23605](https://github.com/open-webui/open-webui/issues/23605) - 🎙️ **Mistral TTS support.** Mistral can now be used as a text-to-speech provider, with admin settings for the API key, base URL, voices, and model selection. [Commit](https://github.com/open-webui/open-webui/commit/4cee67e2be0c80a0b501073ea49a80d13efd1c41) - 🎧 **STT preprocessing bypass option.** Administrators can now enable "AUDIO_STT_SKIP_PREPROCESSING" to send audio files directly to the speech-to-text backend, reducing memory and CPU consumption during large uploads for better transcription performance and stability on constrained deployments. [#23661](https://github.com/open-webui/open-webui/pull/23661) @@ -38,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 📌 **Recently used emojis.** The emoji picker now shows your most recently used emojis at the top, making it faster to find emojis you use often. [Commit](https://github.com/open-webui/open-webui/commit/64da99a32218171d41b3af5acc14783de8dbdf49) - 👆 **Swipe to reply on mobile.** Swiping right on a message now triggers a reply, making it easier to respond on touch devices with a natural gesture. [Commit](https://github.com/open-webui/open-webui/commit/012ce95f27d57bea8911bd63bfb923443c5797ae) - 📱 **Screen-awake voice recording.** Voice recording now keeps the screen awake during active dictation and safely re-acquires wake lock after visibility changes, helping prevent long transcriptions from being cut off on mobile devices. [#23145](https://github.com/open-webui/open-webui/issues/23145) -- ✨ **Improved task list visibility.** The task list automatically hides once all tasks are complete and generation is finished, keeping the chat interface cleaner. [Commit](https://github.com/open-webui/open-webui/commit/0ad397c0482004173d4a8bf4722100acc43db454), [Commit](https://github.com/open-webui/open-webui/commit/4b35d70078a2d7a322566699a43594b3c10b2dda) - 🔔 **Unread chat indicators.** Sidebar chats now show unread status and are marked as read when opened, making it easier to spot conversations with new activity. [Commit](https://github.com/open-webui/open-webui/commit/0638b9f56ce1ba8a496d0e84da2e7fa178b01a3f) - 🔌 **WebSocket reconnect status feedback.** Open WebUI now warns when the real-time connection drops and confirms when it reconnects, while avoiding a reconnect message on the initial page load. [Commit](https://github.com/open-webui/open-webui/commit/1824e69a70e756cfcf543a9fbe4b0780d9b57292) - 📍 **Pinned notes in sidebar.** Notes can now be pinned to the sidebar for quick access, and you can also create a new note directly from the pinned notes section. [Commit](https://github.com/open-webui/open-webui/commit/ecd74f220c7dd671d5705189a3f4493a3868c8bf), [Commit](https://github.com/open-webui/open-webui/commit/f1be85d997439b49fc143d2bcd2dc710f44446c8) @@ -48,11 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 🎨 **Theme updates.** Other windows can now update the app theme directly, keeping the interface in sync when theme changes are triggered externally. [Commit](https://github.com/open-webui/open-webui/commit/9f1b279e88bd22dfff4d2531209536dea6a2f65e) - 🚀 **Async performance and responsiveness improvements.** The core backend database and request paths now run asynchronously across the application, massively improving responsiveness and performance under concurrent load and reducing request blocking during heavy activity. [Commit](https://github.com/open-webui/open-webui/commit/27169124f220e5cea21c88601c731c3749496ab0), [Commit](https://github.com/open-webui/open-webui/commit/8936721414a17832852a90f3ee592af5a8b7232d) - ⚡ **Drawer performance and memory optimization.** Drawer interactions now stay smoother over long sessions by removing stale keyboard listeners on teardown, which reduces memory growth and avoids accumulated event handling overhead. [#23724](https://github.com/open-webui/open-webui/pull/23724#issuecomment-4245840810) -- 🚀 **Chat history memory culling.** Long conversations now stay much more responsive by rendering a smaller message window and unloading off-screen messages with spacer-based virtualization, significantly reducing memory pressure and UI freezing on heavy chats and mobile devices. [#23067](https://github.com/open-webui/open-webui/issues/23067), [Commit](https://github.com/open-webui/open-webui/commit/026903399be73ac4b6c226647110e5662d043a50), [Commit](https://github.com/open-webui/open-webui/commit/9dccd29c94875e6f0ac373c5802cb183296e47ff) +- 🚀 **Chat history memory culling.** Long conversations now stay responsive no matter how many messages they contain. Off-screen messages are unloaded automatically and reloaded as you scroll, keeping memory usage low and the UI smooth on both desktop and mobile. [#23067](https://github.com/open-webui/open-webui/issues/23067), [Commit](https://github.com/open-webui/open-webui/commit/026903399be73ac4b6c226647110e5662d043a50), [Commit](https://github.com/open-webui/open-webui/commit/9dccd29c94875e6f0ac373c5802cb183296e47ff) - 🧵 **Async file and knowledge processing performance.** File processing, knowledge reindexing, and channel message helper paths now consistently await async operations, preventing skipped processing steps and improving reliability and performance of indexing and tool responses. [Commit](https://github.com/open-webui/open-webui/commit/de27a121511a31606f250ba4033490797216a0eb) - 🚀 **Persistent chat payload efficiency.** Persisted chats now use server-side history loading instead of repeatedly resending full message payloads, improving multimodal performance and reducing stale-history overwrite risk across devices. [#19064](https://github.com/open-webui/open-webui/issues/19064), [Commit](https://github.com/open-webui/open-webui/commit/18fe17127a7175579506e7456d3e5aba201371e6), [Commit](https://github.com/open-webui/open-webui/commit/cf4218e688def6f11d195aeda6665ae5b5376b67) - 🧵 **Non-blocking file storage operations.** Uploading, reading, transcribing, and deleting files now offloads storage I/O to background threads, keeping the application responsive during file-heavy workflows. [Commit](https://github.com/open-webui/open-webui/commit/4866bec0f238198a721c952fe18dd04ba643be33) -- 🏃 **Faster automation list loading.** The automations page now loads more smoothly by batching latest-run lookups and avoiding duplicate initial fetches. [Commit](https://github.com/open-webui/open-webui/commit/09f6d7ba57d2aaad83ad0d29d005feb7157776a1) - 🏎️ **Streaming response performance.** Streaming responses now process each output line in a single step instead of two separate yields, reducing async overhead and improving responsiveness during long-running generations. [#23266](https://github.com/open-webui/open-webui/pull/23266) - 🔎 **Faster mention parsing.** Chat text with HTML-like content, file paths, or tool output now parses mentions more efficiently, which helps keep typing and rendering responsive in messages that contain many '<' characters. [#23551](https://github.com/open-webui/open-webui/pull/23551) - 🧪 **Code block rendering performance.** Code blocks now reuse a shared HTML unescape helper, reducing extra browser work when displaying encoded output in chat. [#23553](https://github.com/open-webui/open-webui/pull/23553) @@ -99,6 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 🧩 **Richer Anthropic tool results.** Anthropic-compatible tool calls now preserve more tool result content types, including images and structured search or document outputs, so models can use fuller tool context instead of receiving only plain text fragments. [#23188](https://github.com/open-webui/open-webui/issues/23188), [Commit](https://github.com/open-webui/open-webui/commit/40f5b3d135190dc9a2d8e94dbb1b2cbcbd829132) - 🖼️ **ComfyUI request reliability.** ComfyUI image generation and editing now use shared async connections with consistent SSL handling, making image uploads and workflow runs more reliable under concurrent load. [Commit](https://github.com/open-webui/open-webui/commit/5944eda0ff25a284f7157252683bccede741cbe7) - 🎛️ **Reranking batch size control.** Administrators can now set "RAG_RERANKING_BATCH_SIZE" in Documents settings to control reranking workload size, helping balance retrieval speed and resource usage for their deployment. [Commit](https://github.com/open-webui/open-webui/commit/4d2f18981051205016bd24d39521e25a33581225) +- 🔗 **Shared chat access controls.** You can now control who has access to a shared chat by granting access to specific users or groups, instead of sharing with anyone who has the link. - 🔄 **General improvements.** Various improvements were implemented across the application to enhance performance, stability, and security. - 🌐 **Translation updates.** Translations for Irish, Catalan, German, Simplified Chinese, Hindi, and Portuguese (Brazil) were enhanced and expanded. @@ -111,8 +103,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 🗣️ **Pipeline error detail visibility.** Pipeline inlet and outlet failures now preserve and surface provider error details more reliably in chat error messages, making troubleshooting failed requests much clearer. [Commit](https://github.com/open-webui/open-webui/commit/d5e69f182cd7a6371ab25248f6432b277f83ef23) - 📨 **Shared chat event routing.** Message update and send events now target the chat owner’s event channel, so shared chats receive the correct real-time updates instead of routing events to the acting user. [Commit](https://github.com/open-webui/open-webui/commit/47329b5032ba29716a7e7e973b07c6d9894968e0) - 🔐 **Consistent outbound SSL handling.** External requests for tools, functions, terminals, webhooks, retrieval loaders, audio provider discovery, and OpenAI-compatible embedding calls now consistently apply the configured SSL client setting, improving reliability for deployments that require custom certificate or verification behavior. [Commit](https://github.com/open-webui/open-webui/commit/fd25152076ea7c310e42c9bacc5cd2b544eeae48), [Commit](https://github.com/open-webui/open-webui/commit/56c5bc1d3487020ab886d3332aacc1644c1d6123) -- 🧭 **Scheduled Tasks calendar reliability.** Scheduled Tasks is now handled as a virtual automation calendar that appears only when automation access is available, and calendar selection now filters by stable ID instead of name so event forms behave consistently. [Commit](https://github.com/open-webui/open-webui/commit/1d501cfa3f96b3a9a5f4f7ce996947671fd09f29), [Commit](https://github.com/open-webui/open-webui/commit/24dd5b461eb44d306c823389e0f664c45db042e8) -- 🛡️ **Protected calendar deletion rules.** System and default calendars can no longer be deleted, preventing accidental removal of built-in calendar functionality. [Commit](https://github.com/open-webui/open-webui/commit/51627555bf356c8ec663f4d2f43f2f013eadbce4) - 🖼️ **Image SSL setting support.** Image generation now respects the configured SSL session setting, preventing avoidable connection failures in strict certificate environments. [Commit](https://github.com/open-webui/open-webui/commit/128cf41fcedf2638fc8a6acd850d8b0409be1c4e), [#23777](https://github.com/open-webui/open-webui/issues/23777) - 🗂️ **Folder ownership assignment hardening.** Folder create and update inputs now reject unexpected extra fields, preventing clients from overriding protected values like ownership through mass-assignment payloads. [#23648](https://github.com/open-webui/open-webui/pull/23648) - 🔐 **Knowledge file deletion ownership checks.** Collaborators with knowledge base write access can no longer permanently delete files they do not own, preventing unintended file removal across other linked chats and knowledge bases. [Commit](https://github.com/open-webui/open-webui/commit/914ccf07ef158afe5588b97ed42778c93c439938), [#23636](https://github.com/open-webui/open-webui/pull/23636#issuecomment-4232439454) From 9f61a6f13c5c7668aed894ef12b2711352e1e9c3 Mon Sep 17 00:00:00 2001 From: Tim Baek Date: Tue, 21 Apr 2026 19:37:22 +0900 Subject: [PATCH 117/119] fix --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 27e6faeddf..b6d07a61f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,8 @@ dependencies = [ "python-mimeparse==2.0.0", "sqlalchemy==2.0.48", + "aiosqlite==0.21.0", + "asyncpg==0.30.0", "alembic==1.18.4", "peewee==3.19.0", "peewee-migrate==1.14.3", From f162d4de9077824d613425552f127cf4eb4a38b5 Mon Sep 17 00:00:00 2001 From: Tim Baek Date: Tue, 21 Apr 2026 19:39:44 +0900 Subject: [PATCH 118/119] doc --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0b35b16b..8049dcca1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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.9.1] - 2026-04-21 + +### Fixed + +- 🐛 **Missing `aiosqlite` dependency.** Fixed a startup crash (`ModuleNotFoundError: No module named 'aiosqlite'`) when installing Open WebUI via `pip` or `uv` by adding the missing `aiosqlite` package to `pyproject.toml`. The dependency was listed in `requirements.txt` but not in the published package metadata, so it was not installed automatically. [#23916](https://github.com/open-webui/open-webui/issues/23916) +- 🐛 **Missing `asyncpg` dependency.** Added the missing `asyncpg` package to `pyproject.toml` to prevent the same startup crash for PostgreSQL users. Like `aiosqlite`, it was present in `requirements.txt` but absent from the published package dependencies. + ## [0.9.0] - 2026-04-20 ### Added diff --git a/package-lock.json b/package-lock.json index 8efa79c1b4..e3175ba8a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "open-webui", - "version": "0.9.0", + "version": "0.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "open-webui", - "version": "0.9.0", + "version": "0.9.1", "dependencies": { "@azure/msal-browser": "^4.5.0", "@codemirror/lang-javascript": "^6.2.2", diff --git a/package.json b/package.json index bc1a1c5da3..ab246848c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.9.0", + "version": "0.9.1", "private": true, "scripts": { "dev": "npm run pyodide:fetch && vite dev --host", From d56d74b3877192af37961ea6c78cbcf0ec70f343 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 23 Apr 2026 19:39:06 +0900 Subject: [PATCH 119/119] refac --- src/lib/components/chat/Messages.svelte | 117 +----------------- .../components/chat/Messages/Message.svelte | 13 +- 2 files changed, 13 insertions(+), 117 deletions(-) diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 151f89fb2a..51a75f1242 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -60,102 +60,7 @@ export let messagesCount: number | null = 8; let messagesLoading = false; - // Off-screen message unloading. Heights are measured on scroll so spacers - // always match real sizes — no scroll jumps, no feedback loops needed. - const OVERSCAN = 3; - const DEFAULT_HEIGHT = 150; - let visibleStart = 0; - let visibleEnd = 0; - let messageHeights = new Map(); - let topSpacerHeight = 0; - let bottomSpacerHeight = 0; - let pendingCull = null; - - // Helper: get height for a message (cached or default) - const heightOf = (id) => messageHeights.get(id) ?? DEFAULT_HEIGHT; - - /** Measure all currently rendered message elements and cache their heights */ - const measureMessageHeights = () => { - const elements = document - .getElementById('messages-container') - ?.querySelectorAll('[role="listitem"]'); - if (!elements) return; - - messageHeights = new Map([ - ...messageHeights, - ...Array.from(elements) - .map((el, i) => [messages[visibleStart + i]?.id, el.getBoundingClientRect().height]) - .filter(([id]) => id != null) - ]); - }; - - /** Compute visible range from current scroll position and apply */ - const updateVisibleRange = () => { - const container = document.getElementById('messages-container'); - if (!container || messages.length === 0) return; - - const st = container.scrollTop; - const ch = container.clientHeight; - - // Build prefix sums from measured heights - const prefixSums = messages.reduce( - (acc, m) => [...acc, acc[acc.length - 1] + heightOf(m.id)], - [0] - ); - - const firstVisible = Math.max(0, prefixSums.findIndex((h) => h > st) - 1); - const lastVisible = prefixSums.findIndex((h) => h > st + ch); - - // Only cull messages that have been measured (so spacer height is accurate) - // findIndex returns -1 when all are measured → no limit on culling - const firstUnmeasured = messages.findIndex((m) => !messageHeights.has(m.id)); - const cullLimit = firstUnmeasured === -1 ? messages.length : firstUnmeasured; - - visibleStart = Math.max(0, Math.min(firstVisible - OVERSCAN, cullLimit)); - visibleEnd = Math.min( - messages.length, - (lastVisible === -1 ? messages.length : lastVisible) + OVERSCAN - ); - topSpacerHeight = prefixSums[visibleStart] ?? 0; - bottomSpacerHeight = (prefixSums[messages.length] ?? 0) - (prefixSums[visibleEnd] ?? 0); - }; - - /** Scroll handler: measure every frame, cull via rAF (same throttle as pendingRebuild) */ - const handleContainerScroll = () => { - measureMessageHeights(); - - // Don't cull during progressive loading - if (messagesLoading) return; - - if (!pendingCull) { - pendingCull = requestAnimationFrame(() => { - pendingCull = null; - updateVisibleRange(); - }); - } - }; - - let scrollListenerAttached = false; - - const attachScrollListener = () => { - if (scrollListenerAttached) return; - const container = document.getElementById('messages-container'); - if (!container) return; - - container.addEventListener('scroll', handleContainerScroll, { passive: true }); - scrollListenerAttached = true; - }; - - onMount(() => { - attachScrollListener(); - }); - onDestroy(() => { - const container = document.getElementById('messages-container'); - if (container && scrollListenerAttached) { - container.removeEventListener('scroll', handleContainerScroll); - } - cancelAnimationFrame(pendingCull); cancelAnimationFrame(pendingRebuild); }); @@ -169,12 +74,6 @@ buildMessages(); - // Show all messages during progressive loading (no culling) - visibleStart = 0; - visibleEnd = messages.length; - topSpacerHeight = 0; - bottomSpacerHeight = 0; - await tick(); messagesLoading = false; @@ -201,7 +100,6 @@ } messages = _messages.reverse(); - visibleEnd = messages.length; }; // Throttle message list rebuilds to once per animation frame during streaming. @@ -220,8 +118,6 @@ cancelAnimationFrame(pendingRebuild); pendingRebuild = null; buildMessages(); - // No explicit culling needed — scrollToBottom will fire a scroll event, - // which triggers handleContainerScroll → rAF → updateVisibleRange } else if (_messages) { // Content update (streaming) — throttle to once per frame if (!pendingRebuild) { @@ -570,13 +466,7 @@ {/if}
    - - {#if topSpacerHeight > 0} -
diff --git a/src/lib/components/chat/Messages/Message.svelte b/src/lib/components/chat/Messages/Message.svelte index d9ca32492a..b161aa8556 100644 --- a/src/lib/components/chat/Messages/Message.svelte +++ b/src/lib/components/chat/Messages/Message.svelte @@ -49,7 +49,7 @@ role="listitem" class="flex flex-col justify-between px-5 mb-3 w-full {($settings?.widescreenMode ?? null) ? 'max-w-full' - : 'max-w-5xl'} mx-auto rounded-lg group" + : 'max-w-5xl'} mx-auto rounded-lg group message-listitem" > {#if history.messages[messageId]} {#if history.messages[messageId].role === 'user'} @@ -128,3 +128,14 @@ {/if} {/if}
+ + +