From 49199819db69837f31227cfef3674fbaf8abca72 Mon Sep 17 00:00:00 2001 From: Aryan Kothari <87589047+thearyadev@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:24:05 -0400 Subject: [PATCH 01/81] add: stores for pagination state --- src/lib/stores/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 1b0257c4b9..60a6171854 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -41,6 +41,9 @@ export const showSettings = writable(false); export const showArchivedChats = writable(false); export const showChangelog = writable(false); export const showCallOverlay = writable(false); +export const scrollPaginationEnabled = writable(true); +export const pageSkip = writable(0); +export const pageLimit = writable(-1); export type Model = OpenAIModel | OllamaModel; From 519375b4c090d864395b6cacecc5c3b9bb3a0d93 Mon Sep 17 00:00:00 2001 From: Aryan Kothari <87589047+thearyadev@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:05:52 -0400 Subject: [PATCH 02/81] add: skip and limit use in query - limit default changed to -1 --- backend/apps/webui/models/chats.py | 9 +++++---- backend/apps/webui/routers/chats.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/apps/webui/models/chats.py b/backend/apps/webui/models/chats.py index abde4f2b31..d504b18c3f 100644 --- a/backend/apps/webui/models/chats.py +++ b/backend/apps/webui/models/chats.py @@ -250,7 +250,7 @@ class ChatTable: user_id: str, include_archived: bool = False, skip: int = 0, - limit: int = 50, + limit: int = -1, ) -> List[ChatTitleIdResponse]: with get_db() as db: query = db.query(Chat).filter_by(user_id=user_id) @@ -260,9 +260,10 @@ class ChatTable: all_chats = ( query.order_by(Chat.updated_at.desc()) # limit cols - .with_entities( - Chat.id, Chat.title, Chat.updated_at, Chat.created_at - ).all() + .with_entities(Chat.id, Chat.title, Chat.updated_at, Chat.created_at) + .limit(limit) + .offset(skip) + .all() ) # result has to be destrctured from sqlalchemy `row` and mapped to a dict since the `ChatModel`is not the returned dataclass. return [ diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 80308a451b..47c7c4a87a 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -43,7 +43,7 @@ router = APIRouter() @router.get("/", response_model=List[ChatTitleIdResponse]) @router.get("/list", response_model=List[ChatTitleIdResponse]) async def get_session_user_chat_list( - user=Depends(get_verified_user), skip: int = 0, limit: int = 50 + user=Depends(get_verified_user), skip: int = 0, limit: int = -1 ): return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit) From d11961626c56c30deb165f202e842d2ce283ccff Mon Sep 17 00:00:00 2001 From: Aryan Kothari <87589047+thearyadev@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:15:49 -0400 Subject: [PATCH 03/81] add: use skip and limit in api call --- src/lib/apis/chats/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index b046f1b10d..8ff12f4b68 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -32,10 +32,10 @@ export const createNewChat = async (token: string, chat: object) => { return res; }; -export const getChatList = async (token: string = '') => { +export const getChatList = async (token: string = '', skip: number = 0, limit: number = -1) => { let error = null; - const res = await fetch(`${WEBUI_API_BASE_URL}/chats/`, { + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?skip=${skip}&limit=${limit}`, { method: 'GET', headers: { Accept: 'application/json', From 62dc486c8577bef6d0c65fd3777b9425bd85a06d Mon Sep 17 00:00:00 2001 From: Aryan Kothari <87589047+thearyadev@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:19:14 -0400 Subject: [PATCH 04/81] add: add paginated scroll handler --- src/lib/components/layout/Sidebar.svelte | 77 +++++++++++++++++++++--- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 49a0e2ebba..d8434e6a24 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -11,7 +11,10 @@ showSidebar, mobile, showArchivedChats, - pinnedChats + pinnedChats, + pageSkip, + pageLimit, + scrollPaginationEnabled } from '$lib/stores'; import { onMount, getContext, tick } from 'svelte'; @@ -49,6 +52,12 @@ let showDropdown = false; let filteredChatList = []; + let paginationScrollThreashold = 0.6; + let nextPageLoading = false; + let tagView = false; + let chatPagniationComplete = false; + + pageLimit.set(20); $: filteredChatList = $chats.filter((chat) => { if (search === '') { @@ -84,7 +93,7 @@ showSidebar.set(window.innerWidth > BREAKPOINT); await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); - await chats.set(await getChatList(localStorage.token)); + await chats.set(await getChatList(localStorage.token, $pageSkip, $pageLimit)); let touchstart; let touchend; @@ -185,7 +194,9 @@ await tick(); goto('/'); } - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); await pinnedChats.set(await getChatListByTagName(localStorage.token, 'pinned')); } }; @@ -235,6 +246,9 @@ ? '' : 'invisible'}" > +

+ Chats loaded: {$chats.length} +

{ + on:focus={async () => { + // loading all chats. disable pagination on scrol. + scrollPaginationEnabled.set(false); + // subsequent queries will calculate page size to rehydrate the ui. + // since every chat is already loaded, the calculation should now load all chats. + pageSkip.set(0); + pageLimit.set(-1); + await chats.set(await getChatList(localStorage.token)); // when searching, load all chats + enrichChatsWithContent($chats); }} /> @@ -422,7 +444,13 @@
{/if} -
+
{ + if (!$scrollPaginationEnabled) return; + if (tagView) return; + if (nextPageLoading) return; + if (chatPagniationComplete) return; + + const maxScroll = e.target.scrollHeight - e.target.clientHeight; + const currentPos = e.target.scrollTop; + const ratio = currentPos / maxScroll; + if (ratio >= paginationScrollThreashold) { + nextPageLoading = true; + pageSkip.set($pageSkip + 1); + // extend existing chats + const nextPageChats = await getChatList( + localStorage.token, + $pageSkip * $pageLimit, + $pageLimit + ); + // once the bottom of the list has been reached (no results) there is no need to continue querying + chatPagniationComplete = nextPageChats.length === 0; + await chats.set([...$chats, ...nextPageChats]); + nextPageLoading = false; + } + }} + > {#each filteredChatList as chat, idx} {#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
Date: Thu, 1 Aug 2024 15:20:36 -0400 Subject: [PATCH 05/81] refactor: uses of `chats.set(...)` support pagi sidebar --- src/lib/components/chat/Chat.svelte | 31 ++++++++++++++----- src/lib/components/chat/Messages.svelte | 4 +-- src/lib/components/chat/Settings/Chats.svelte | 21 +++++++++++-- src/lib/components/chat/Tags.svelte | 13 ++++++-- .../components/layout/Sidebar/ChatItem.svelte | 20 +++++++++--- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 24e74e6955..2da9d1a8e2 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -25,7 +25,9 @@ user, socket, showCallOverlay, - tools + tools, + pageSkip, + pageLimit } from '$lib/stores'; import { convertMessagesToHistory, @@ -418,7 +420,9 @@ params: params, files: chatFiles }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); } } }; @@ -464,7 +468,9 @@ params: params, files: chatFiles }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); } } }; @@ -624,7 +630,9 @@ tags: [], timestamp: Date.now() }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); await chatId.set(chat.id); } else { await chatId.set('local'); @@ -700,7 +708,8 @@ }) ); - await chats.set(await getChatList(localStorage.token)); + await chats.set(await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit)); + return _responses; }; @@ -947,7 +956,9 @@ params: params, files: chatFiles }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); } } } else { @@ -1220,7 +1231,9 @@ params: params, files: chatFiles }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); } } } else { @@ -1385,7 +1398,9 @@ if ($settings.saveChatHistory ?? true) { chat = await updateChatById(localStorage.token, _chatId, { title: _title }); - await chats.set(await getChatList(localStorage.token)); + await chats.set( + await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) + ); } }; diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index e46e931432..c798de9233 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -1,6 +1,6 @@ @@ -49,7 +51,8 @@
From 12c21fac22101f8f3a5fa8fcd2c9d15fd42ee89e Mon Sep 17 00:00:00 2001 From: Michael Poluektov Date: Sat, 3 Aug 2024 14:24:26 +0100 Subject: [PATCH 11/81] refac: apps/openai/main.py and utils --- backend/apps/openai/main.py | 192 +++++++++------------------- backend/apps/socket/main.py | 27 ++-- backend/apps/webui/main.py | 45 +------ backend/apps/webui/routers/tools.py | 31 ++--- backend/main.py | 28 ++-- backend/utils/misc.py | 43 +++++++ backend/utils/task.py | 3 +- backend/utils/utils.py | 9 +- 8 files changed, 148 insertions(+), 230 deletions(-) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index c712709a5c..be8ec64899 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -1,6 +1,6 @@ -from fastapi import FastAPI, Request, Response, HTTPException, Depends +from fastapi import FastAPI, Request, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import StreamingResponse, JSONResponse, FileResponse +from fastapi.responses import StreamingResponse, FileResponse import requests import aiohttp @@ -12,16 +12,12 @@ from pydantic import BaseModel from starlette.background import BackgroundTask from apps.webui.models.models import Models -from apps.webui.models.users import Users from constants import ERROR_MESSAGES from utils.utils import ( - decode_token, - get_verified_user, get_verified_user, get_admin_user, ) -from utils.task import prompt_template -from utils.misc import add_or_update_system_message +from utils.misc import apply_model_params_to_body, apply_model_system_prompt_to_body from config import ( SRC_LOG_LEVELS, @@ -69,8 +65,6 @@ app.state.MODELS = {} async def check_url(request: Request, call_next): if len(app.state.MODELS) == 0: await get_all_models() - else: - pass response = await call_next(request) return response @@ -175,7 +169,7 @@ async def speech(request: Request, user=Depends(get_verified_user)): res = r.json() if "error" in res: error_detail = f"External: {res['error']}" - except: + except Exception: error_detail = f"External: {e}" raise HTTPException( @@ -234,64 +228,58 @@ def merge_models_lists(model_lists): return merged_list -async def get_all_models(raw: bool = False): +def is_openai_api_disabled(): + api_keys = app.state.config.OPENAI_API_KEYS + no_keys = len(api_keys) == 1 and api_keys[0] == "" + return no_keys or not app.state.config.ENABLE_OPENAI_API + + +async def get_all_models_raw() -> list: + if is_openai_api_disabled(): + return [] + + # Check if API KEYS length is same than API URLS length + num_urls = len(app.state.config.OPENAI_API_BASE_URLS) + num_keys = len(app.state.config.OPENAI_API_KEYS) + + if num_keys != num_urls: + # if there are more keys than urls, remove the extra keys + if num_keys > num_urls: + new_keys = app.state.config.OPENAI_API_KEYS[:num_urls] + app.state.config.OPENAI_API_KEYS = new_keys + # if there are more urls than keys, add empty keys + else: + app.state.config.OPENAI_API_KEYS += [""] * (num_urls - num_keys) + + tasks = [ + fetch_url(f"{url}/models", app.state.config.OPENAI_API_KEYS[idx]) + for idx, url in enumerate(app.state.config.OPENAI_API_BASE_URLS) + ] + + responses = await asyncio.gather(*tasks) + log.debug(f"get_all_models:responses() {responses}") + + return responses + + +async def get_all_models() -> dict[str, list]: log.info("get_all_models()") + if is_openai_api_disabled(): + return {"data": []} - if ( - len(app.state.config.OPENAI_API_KEYS) == 1 - and app.state.config.OPENAI_API_KEYS[0] == "" - ) or not app.state.config.ENABLE_OPENAI_API: - models = {"data": []} - else: - # Check if API KEYS length is same than API URLS length - if len(app.state.config.OPENAI_API_KEYS) != len( - app.state.config.OPENAI_API_BASE_URLS - ): - # if there are more keys than urls, remove the extra keys - if len(app.state.config.OPENAI_API_KEYS) > len( - app.state.config.OPENAI_API_BASE_URLS - ): - app.state.config.OPENAI_API_KEYS = app.state.config.OPENAI_API_KEYS[ - : len(app.state.config.OPENAI_API_BASE_URLS) - ] - # if there are more urls than keys, add empty keys - else: - app.state.config.OPENAI_API_KEYS += [ - "" - for _ in range( - len(app.state.config.OPENAI_API_BASE_URLS) - - len(app.state.config.OPENAI_API_KEYS) - ) - ] + responses = await get_all_models_raw() - tasks = [ - fetch_url(f"{url}/models", app.state.config.OPENAI_API_KEYS[idx]) - for idx, url in enumerate(app.state.config.OPENAI_API_BASE_URLS) - ] + def extract_data(response): + if response and "data" in response: + return response["data"] + if isinstance(response, list): + return response + return None - responses = await asyncio.gather(*tasks) - log.debug(f"get_all_models:responses() {responses}") + models = {"data": merge_models_lists(map(extract_data, responses))} - if raw: - return responses - - models = { - "data": merge_models_lists( - list( - map( - lambda response: ( - response["data"] - if (response and "data" in response) - else (response if isinstance(response, list) else None) - ), - responses, - ) - ) - ) - } - - log.debug(f"models: {models}") - app.state.MODELS = {model["id"]: model for model in models["data"]} + log.debug(f"models: {models}") + app.state.MODELS = {model["id"]: model for model in models["data"]} return models @@ -299,7 +287,7 @@ async def get_all_models(raw: bool = False): @app.get("/models") @app.get("/models/{url_idx}") async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_user)): - if url_idx == None: + if url_idx is None: models = await get_all_models() if app.state.config.ENABLE_MODEL_FILTER: if user.role == "user": @@ -340,7 +328,7 @@ async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_us res = r.json() if "error" in res: error_detail = f"External: {res['error']}" - except: + except Exception: error_detail = f"External: {e}" raise HTTPException( @@ -358,8 +346,7 @@ async def generate_chat_completion( ): idx = 0 payload = {**form_data} - if "metadata" in payload: - del payload["metadata"] + payload.pop("metadata") model_id = form_data.get("model") model_info = Models.get_model_by_id(model_id) @@ -368,70 +355,9 @@ async def generate_chat_completion( if model_info.base_model_id: payload["model"] = model_info.base_model_id - model_info.params = model_info.params.model_dump() - - if model_info.params: - if ( - model_info.params.get("temperature", None) is not None - and payload.get("temperature") is None - ): - payload["temperature"] = float(model_info.params.get("temperature")) - - if model_info.params.get("top_p", None) and payload.get("top_p") is None: - payload["top_p"] = int(model_info.params.get("top_p", None)) - - if ( - model_info.params.get("max_tokens", None) - and payload.get("max_tokens") is None - ): - payload["max_tokens"] = int(model_info.params.get("max_tokens", None)) - - if ( - model_info.params.get("frequency_penalty", None) - and payload.get("frequency_penalty") is None - ): - payload["frequency_penalty"] = int( - model_info.params.get("frequency_penalty", None) - ) - - if ( - model_info.params.get("seed", None) is not None - and payload.get("seed") is None - ): - payload["seed"] = model_info.params.get("seed", None) - - if model_info.params.get("stop", None) and payload.get("stop") is None: - payload["stop"] = ( - [ - bytes(stop, "utf-8").decode("unicode_escape") - for stop in model_info.params["stop"] - ] - if model_info.params.get("stop", None) - else None - ) - - system = model_info.params.get("system", None) - if system: - system = prompt_template( - system, - **( - { - "user_name": user.name, - "user_location": ( - user.info.get("location") if user.info else None - ), - } - if user - else {} - ), - ) - if payload.get("messages"): - payload["messages"] = add_or_update_system_message( - system, payload["messages"] - ) - - else: - pass + params = model_info.params.model_dump() + payload = apply_model_params_to_body(params, payload) + payload = apply_model_system_prompt_to_body(params, payload, user) model = app.state.MODELS[payload.get("model")] idx = model["urlIdx"] @@ -506,7 +432,7 @@ async def generate_chat_completion( print(res) if "error" in res: error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}" - except: + except Exception: error_detail = f"External: {e}" raise HTTPException(status_code=r.status if r else 500, detail=error_detail) finally: @@ -569,7 +495,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): print(res) if "error" in res: error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}" - except: + except Exception: error_detail = f"External: {e}" raise HTTPException(status_code=r.status if r else 500, detail=error_detail) finally: diff --git a/backend/apps/socket/main.py b/backend/apps/socket/main.py index 1d98d37ff1..fcffca4209 100644 --- a/backend/apps/socket/main.py +++ b/backend/apps/socket/main.py @@ -44,23 +44,26 @@ async def user_join(sid, data): print("user-join", sid, data) auth = data["auth"] if "auth" in data else None + if not auth or "token" not in auth: + return - if auth and "token" in auth: - data = decode_token(auth["token"]) + data = decode_token(auth["token"]) + if data is None or "id" not in data: + return - if data is not None and "id" in data: - user = Users.get_user_by_id(data["id"]) + user = Users.get_user_by_id(data["id"]) + if not user: + return - if user: - SESSION_POOL[sid] = user.id - if user.id in USER_POOL: - USER_POOL[user.id].append(sid) - else: - USER_POOL[user.id] = [sid] + SESSION_POOL[sid] = user.id + if user.id in USER_POOL: + USER_POOL[user.id].append(sid) + else: + USER_POOL[user.id] = [sid] - print(f"user {user.name}({user.id}) connected with session ID {sid}") + print(f"user {user.name}({user.id}) connected with session ID {sid}") - await sio.emit("user-count", {"count": len(set(USER_POOL))}) + await sio.emit("user-count", {"count": len(set(USER_POOL))}) @sio.on("user-count") diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index 972562a04d..a0b9f50085 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -22,9 +22,9 @@ from apps.webui.utils import load_function_module_by_id from utils.misc import ( openai_chat_chunk_message_template, openai_chat_completion_message_template, - add_or_update_system_message, + apply_model_params_to_body, + apply_model_system_prompt_to_body, ) -from utils.task import prompt_template from config import ( @@ -269,47 +269,6 @@ def get_function_params(function_module, form_data, user, extra_params={}): return params -# inplace function: form_data is modified -def apply_model_params_to_body(params: dict, form_data: dict) -> dict: - if not params: - return form_data - - mappings = { - "temperature": float, - "top_p": int, - "max_tokens": int, - "frequency_penalty": int, - "seed": lambda x: x, - "stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x], - } - - for key, cast_func in mappings.items(): - if (value := params.get(key)) is not None: - form_data[key] = cast_func(value) - - return form_data - - -# inplace function: form_data is modified -def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict: - system = params.get("system", None) - if not system: - return form_data - - if user: - template_params = { - "user_name": user.name, - "user_location": user.info.get("location") if user.info else None, - } - else: - template_params = {} - system = prompt_template(system, **template_params) - form_data["messages"] = add_or_update_system_message( - system, form_data.get("messages", []) - ) - return form_data - - async def generate_function_chat_completion(form_data, user): model_id = form_data.get("model") model_info = Models.get_model_by_id(model_id) diff --git a/backend/apps/webui/routers/tools.py b/backend/apps/webui/routers/tools.py index ea9db8180b..7e60fe4d1e 100644 --- a/backend/apps/webui/routers/tools.py +++ b/backend/apps/webui/routers/tools.py @@ -1,12 +1,8 @@ -from fastapi import Depends, FastAPI, HTTPException, status, Request -from datetime import datetime, timedelta -from typing import List, Union, Optional +from fastapi import Depends, HTTPException, status, Request +from typing import List, Optional from fastapi import APIRouter -from pydantic import BaseModel -import json -from apps.webui.models.users import Users from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse from apps.webui.utils import load_toolkit_module_by_id @@ -14,7 +10,6 @@ from utils.utils import get_admin_user, get_verified_user from utils.tools import get_tools_specs from constants import ERROR_MESSAGES -from importlib import util import os from pathlib import Path @@ -69,7 +64,7 @@ async def create_new_toolkit( form_data.id = form_data.id.lower() toolkit = Tools.get_tool_by_id(form_data.id) - if toolkit == None: + if toolkit is None: toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py") try: with open(toolkit_path, "w") as tool_file: @@ -98,7 +93,7 @@ async def create_new_toolkit( print(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) else: raise HTTPException( @@ -170,7 +165,7 @@ async def update_toolkit_by_id( except Exception as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) @@ -210,7 +205,7 @@ async def get_toolkit_valves_by_id(id: str, user=Depends(get_admin_user)): except Exception as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) else: raise HTTPException( @@ -233,7 +228,7 @@ async def get_toolkit_valves_spec_by_id( if id in request.app.state.TOOLS: toolkit_module = request.app.state.TOOLS[id] else: - toolkit_module, frontmatter = load_toolkit_module_by_id(id) + toolkit_module, _ = load_toolkit_module_by_id(id) request.app.state.TOOLS[id] = toolkit_module if hasattr(toolkit_module, "Valves"): @@ -261,7 +256,7 @@ async def update_toolkit_valves_by_id( if id in request.app.state.TOOLS: toolkit_module = request.app.state.TOOLS[id] else: - toolkit_module, frontmatter = load_toolkit_module_by_id(id) + toolkit_module, _ = load_toolkit_module_by_id(id) request.app.state.TOOLS[id] = toolkit_module if hasattr(toolkit_module, "Valves"): @@ -276,7 +271,7 @@ async def update_toolkit_valves_by_id( print(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) else: raise HTTPException( @@ -306,7 +301,7 @@ async def get_toolkit_user_valves_by_id(id: str, user=Depends(get_verified_user) except Exception as e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) else: raise HTTPException( @@ -324,7 +319,7 @@ async def get_toolkit_user_valves_spec_by_id( if id in request.app.state.TOOLS: toolkit_module = request.app.state.TOOLS[id] else: - toolkit_module, frontmatter = load_toolkit_module_by_id(id) + toolkit_module, _ = load_toolkit_module_by_id(id) request.app.state.TOOLS[id] = toolkit_module if hasattr(toolkit_module, "UserValves"): @@ -348,7 +343,7 @@ async def update_toolkit_user_valves_by_id( if id in request.app.state.TOOLS: toolkit_module = request.app.state.TOOLS[id] else: - toolkit_module, frontmatter = load_toolkit_module_by_id(id) + toolkit_module, _ = load_toolkit_module_by_id(id) request.app.state.TOOLS[id] = toolkit_module if hasattr(toolkit_module, "UserValves"): @@ -365,7 +360,7 @@ async def update_toolkit_user_valves_by_id( print(e) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), + detail=ERROR_MESSAGES.DEFAULT(str(e)), ) else: raise HTTPException( diff --git a/backend/main.py b/backend/main.py index a7dd8bc23b..3e1a58d905 100644 --- a/backend/main.py +++ b/backend/main.py @@ -36,6 +36,7 @@ from apps.ollama.main import ( from apps.openai.main import ( app as openai_app, get_all_models as get_openai_models, + get_all_models_raw as get_openai_models_raw, generate_chat_completion as generate_openai_chat_completion, ) @@ -957,7 +958,7 @@ async def get_all_models(): custom_models = Models.get_all_models() for custom_model in custom_models: - if custom_model.base_model_id == None: + if custom_model.base_model_id is None: for model in models: if ( custom_model.id == model["id"] @@ -1656,13 +1657,13 @@ async def get_tools_function_calling(form_data: dict, user=Depends(get_verified_ @app.get("/api/pipelines/list") async def get_pipelines_list(user=Depends(get_admin_user)): - responses = await get_openai_models(raw=True) + responses = await get_openai_models_raw() print(responses) urlIdxs = [ idx for idx, response in enumerate(responses) - if response != None and "pipelines" in response + if response is not None and "pipelines" in response ] return { @@ -1723,7 +1724,7 @@ async def upload_pipeline( res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -1769,7 +1770,7 @@ async def add_pipeline(form_data: AddPipelineForm, user=Depends(get_admin_user)) res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -1811,7 +1812,7 @@ async def delete_pipeline(form_data: DeletePipelineForm, user=Depends(get_admin_ res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -1844,7 +1845,7 @@ async def get_pipelines(urlIdx: Optional[int] = None, user=Depends(get_admin_use res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -1859,7 +1860,6 @@ async def get_pipeline_valves( pipeline_id: str, user=Depends(get_admin_user), ): - models = await get_all_models() r = None try: url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx] @@ -1898,8 +1898,6 @@ async def get_pipeline_valves_spec( pipeline_id: str, user=Depends(get_admin_user), ): - models = await get_all_models() - r = None try: url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx] @@ -1922,7 +1920,7 @@ async def get_pipeline_valves_spec( res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -1938,8 +1936,6 @@ async def update_pipeline_valves( form_data: dict, user=Depends(get_admin_user), ): - models = await get_all_models() - r = None try: url = openai_app.state.config.OPENAI_API_BASE_URLS[urlIdx] @@ -1967,7 +1963,7 @@ async def update_pipeline_valves( res = r.json() if "detail" in res: detail = res["detail"] - except: + except Exception: pass raise HTTPException( @@ -2068,7 +2064,7 @@ async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): @app.get("/api/version") -async def get_app_config(): +async def get_app_version(): return { "version": VERSION, } @@ -2091,7 +2087,7 @@ async def get_app_latest_release_version(): latest_version = data["tag_name"] return {"current": VERSION, "latest": latest_version[1:]} - except aiohttp.ClientError as e: + except aiohttp.ClientError: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=ERROR_MESSAGES.RATE_LIMIT_EXCEEDED, diff --git a/backend/utils/misc.py b/backend/utils/misc.py index c4e2eda6f0..8a6badd05b 100644 --- a/backend/utils/misc.py +++ b/backend/utils/misc.py @@ -6,6 +6,8 @@ from typing import Optional, List, Tuple import uuid import time +from utils.task import prompt_template + def get_last_user_message_item(messages: List[dict]) -> Optional[dict]: for message in reversed(messages): @@ -111,6 +113,47 @@ def openai_chat_completion_message_template(model: str, message: str): template["choices"][0]["finish_reason"] = "stop" +# inplace function: form_data is modified +def apply_model_system_prompt_to_body(params: dict, form_data: dict, user) -> dict: + system = params.get("system", None) + if not system: + return form_data + + if user: + template_params = { + "user_name": user.name, + "user_location": user.info.get("location") if user.info else None, + } + else: + template_params = {} + system = prompt_template(system, **template_params) + form_data["messages"] = add_or_update_system_message( + system, form_data.get("messages", []) + ) + return form_data + + +# inplace function: form_data is modified +def apply_model_params_to_body(params: dict, form_data: dict) -> dict: + if not params: + return form_data + + mappings = { + "temperature": float, + "top_p": int, + "max_tokens": int, + "frequency_penalty": int, + "seed": lambda x: x, + "stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x], + } + + for key, cast_func in mappings.items(): + if (value := params.get(key)) is not None: + form_data[key] = cast_func(value) + + return form_data + + def get_gravatar_url(email): # Trim leading and trailing whitespace from # an email address and force all characters diff --git a/backend/utils/task.py b/backend/utils/task.py index 053a526a80..1b2276c9c5 100644 --- a/backend/utils/task.py +++ b/backend/utils/task.py @@ -6,7 +6,7 @@ from typing import Optional def prompt_template( - template: str, user_name: str = None, user_location: str = None + template: str, user_name: Optional[str] = None, user_location: Optional[str] = None ) -> str: # Get the current date current_date = datetime.now() @@ -83,7 +83,6 @@ def title_generation_template( def search_query_generation_template( template: str, prompt: str, user: Optional[dict] = None ) -> str: - def replacement_function(match): full_match = match.group(0) start_length = match.group(1) diff --git a/backend/utils/utils.py b/backend/utils/utils.py index fbc539af5c..288db1fb54 100644 --- a/backend/utils/utils.py +++ b/backend/utils/utils.py @@ -1,15 +1,12 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi import HTTPException, status, Depends, Request -from sqlalchemy.orm import Session from apps.webui.models.users import Users -from pydantic import BaseModel from typing import Union, Optional from constants import ERROR_MESSAGES from passlib.context import CryptContext from datetime import datetime, timedelta -import requests import jwt import uuid import logging @@ -54,7 +51,7 @@ def decode_token(token: str) -> Optional[dict]: try: decoded = jwt.decode(token, SESSION_SECRET, algorithms=[ALGORITHM]) return decoded - except Exception as e: + except Exception: return None @@ -71,7 +68,7 @@ def get_http_authorization_cred(auth_header: str): try: scheme, credentials = auth_header.split(" ") return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) - except: + except Exception: raise ValueError(ERROR_MESSAGES.INVALID_TOKEN) @@ -96,7 +93,7 @@ def get_current_user( # auth by jwt token data = decode_token(token) - if data != None and "id" in data: + if data is not None and "id" in data: user = Users.get_user_by_id(data["id"]) if user is None: raise HTTPException( From 028eb7b351bd44e92a2231b39d521dd820ab83d5 Mon Sep 17 00:00:00 2001 From: Clivia <132346501+Yanyutin753@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:00:37 +0800 Subject: [PATCH 12/81] =?UTF-8?q?=E2=9C=A8=20Better=20Practice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Better Practice --- .../components/chat/MessageInput/Models.svelte | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib/components/chat/MessageInput/Models.svelte b/src/lib/components/chat/MessageInput/Models.svelte index ccd455a994..249eb96a5b 100644 --- a/src/lib/components/chat/MessageInput/Models.svelte +++ b/src/lib/components/chat/MessageInput/Models.svelte @@ -21,10 +21,8 @@ let filteredModels = []; $: filteredModels = $models - .filter( - (p) => - p.name.toLowerCase().includes(prompt.toLowerCase().split(' ')?.at(0)?.substring(1) ?? '') && - p?.info?.meta?.hidden + .filter((p) => + p.name.toLowerCase().includes(prompt.toLowerCase().split(' ')?.at(0)?.substring(1) ?? '') ) .sort((a, b) => a.name.localeCompare(b.name)); @@ -149,9 +147,9 @@
{#each filteredModels as model, modelIdx} {/each}
From af9e0cc33a7f6470d9f6ce7bc1692ca233eed287 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 3 Aug 2024 16:01:25 +0200 Subject: [PATCH 13/81] fix --- src/lib/components/chat/MessageInput/Models.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/MessageInput/Models.svelte b/src/lib/components/chat/MessageInput/Models.svelte index 249eb96a5b..c4655991fc 100644 --- a/src/lib/components/chat/MessageInput/Models.svelte +++ b/src/lib/components/chat/MessageInput/Models.svelte @@ -149,7 +149,7 @@
From 3fa6c413033561c5a9edaa8668665467aa639de1 Mon Sep 17 00:00:00 2001 From: Aryan Kothari <87589047+thearyadev@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:17:03 -0400 Subject: [PATCH 25/81] chore: remove debug ui elements --- src/lib/components/layout/Sidebar.svelte | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index fb5804a214..0095a8fcd7 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -250,16 +250,6 @@ ? '' : 'invisible'}" > -

- Chats loaded: {$chats.length} -

- -

- Pagination Enabled: {$scrollPaginationEnabled} -

-

- Final Page Reached: {chatPagniationComplete} -

Date: Sun, 4 Aug 2024 09:56:04 +0800 Subject: [PATCH 26/81] Add option to toggle scrolling to bottom when switching between branches --- src/lib/components/chat/Messages.svelte | 24 ++++++++------- .../components/chat/Settings/Interface.svelte | 29 +++++++++++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index e46e931432..d2a700b5cc 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -146,12 +146,14 @@ await tick(); - const element = document.getElementById('messages-container'); - autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; + if ($settings.scrollOnBranchChange) { + const element = document.getElementById('messages-container'); + autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; - setTimeout(() => { - scrollToBottom(); - }, 100); + setTimeout(() => { + scrollToBottom(); + }, 100); + } }; const showNextMessage = async (message) => { @@ -195,12 +197,14 @@ await tick(); - const element = document.getElementById('messages-container'); - autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; + if ($settings.scrollOnBranchChange) { + const element = document.getElementById('messages-container'); + autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; - setTimeout(() => { - scrollToBottom(); - }, 100); + setTimeout(() => { + scrollToBottom(); + }, 100); + } }; const deleteMessageHandler = async (messageId) => { diff --git a/src/lib/components/chat/Settings/Interface.svelte b/src/lib/components/chat/Settings/Interface.svelte index e6eefaefcd..d5e198890b 100644 --- a/src/lib/components/chat/Settings/Interface.svelte +++ b/src/lib/components/chat/Settings/Interface.svelte @@ -22,6 +22,7 @@ let responseAutoCopy = false; let widescreenMode = false; let splitLargeChunks = false; + let scrollOnBranchChange = true; let userLocation = false; // Interface @@ -39,6 +40,11 @@ saveSettings({ splitLargeChunks: splitLargeChunks }); }; + const togglesScrollOnBranchChange = async () => { + scrollOnBranchChange = !scrollOnBranchChange; + saveSettings({ scrollOnBranchChange: scrollOnBranchChange }); + }; + const togglewidescreenMode = async () => { widescreenMode = !widescreenMode; saveSettings({ widescreenMode: widescreenMode }); @@ -141,6 +147,7 @@ chatBubble = $settings.chatBubble ?? true; widescreenMode = $settings.widescreenMode ?? false; splitLargeChunks = $settings.splitLargeChunks ?? false; + scrollOnBranchChange = $settings.scrollOnBranchChange ?? true; chatDirection = $settings.chatDirection ?? 'LTR'; userLocation = $settings.userLocation ?? false; @@ -318,6 +325,28 @@
+
+
+
+ {$i18n.t('Scroll to bottom when switching between branches')} +
+ + +
+
+
From 482a7723b2f35f9fafb4ecfc78bf1813f84fde99 Mon Sep 17 00:00:00 2001 From: Zhuoran Date: Sun, 4 Aug 2024 11:33:20 +0800 Subject: [PATCH 27/81] Default to true --- src/lib/components/chat/Messages.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index d2a700b5cc..319f0f1708 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -146,7 +146,7 @@ await tick(); - if ($settings.scrollOnBranchChange) { + if ($settings?.scrollOnBranchChange ?? true) { const element = document.getElementById('messages-container'); autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; @@ -197,7 +197,7 @@ await tick(); - if ($settings.scrollOnBranchChange) { + if ($settings?.scrollOnBranchChange ?? true) { const element = document.getElementById('messages-container'); autoScroll = element.scrollHeight - element.scrollTop <= element.clientHeight + 50; From 7c9bed83d2259ef82a9a9f38e21aafc0ad2e5694 Mon Sep 17 00:00:00 2001 From: Karl Lee <61072264+KarlLee830@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:00:42 +0800 Subject: [PATCH 28/81] i18n: Update Chinese Translation --- src/lib/i18n/locales/zh-CN/translation.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 11dcb0a85e..9db1fd8e8a 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -15,7 +15,7 @@ "Account": "账号", "Account Activation Pending": "账号待激活", "Accurate information": "提供的信息很准确", - "Actions": "", + "Actions": "自动化", "Active Users": "当前在线用户", "Add": "添加", "Add a model id": "添加一个模型 ID", @@ -28,7 +28,7 @@ "Add Memory": "添加记忆", "Add message": "添加消息", "Add Model": "添加模型", - "Add Tag": "", + "Add Tag": "添加标签", "Add Tags": "添加标签", "Add User": "添加用户", "Adjusting these settings will apply changes universally to all users.": "调整这些设置将会对所有用户应用更改。", @@ -375,7 +375,7 @@ "Memory deleted successfully": "记忆删除成功", "Memory updated successfully": "记忆更新成功", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "创建链接后发送的消息不会被共享。具有 URL 的用户将能够查看共享对话。", - "Min P": "", + "Min P": "Min P", "Minimum Score": "最低分", "Mirostat": "Mirostat", "Mirostat Eta": "Mirostat Eta", @@ -621,7 +621,7 @@ "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "请联系管理员以访问。管理员可以在后台管理面板中管理用户状态。", "To add documents here, upload them to the \"Documents\" workspace first.": "要在此处添加文档,请先将它们上传到工作空间中的“文档”内。", "to chat input.": "到对话输入。", - "To select actions here, add them to the \"Functions\" workspace first.": "", + "To select actions here, add them to the \"Functions\" workspace first.": "要在这里选择自动化,请先将它们添加到工作空间中的“函数”。", "To select filters here, add them to the \"Functions\" workspace first.": "要在这里选择过滤器,请先将它们添加到工作空间中的“函数”。", "To select toolkits here, add them to the \"Tools\" workspace first.": "要在这里选择工具包,请先将它们添加到工作空间中的“工具”。", "Today": "今天", From f8ba0334e828923389bced66a1b3d4e34cb26747 Mon Sep 17 00:00:00 2001 From: Michael Poluektov Date: Sun, 4 Aug 2024 12:10:02 +0100 Subject: [PATCH 29/81] fix: non streaming functions --- backend/utils/misc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/utils/misc.py b/backend/utils/misc.py index c4e2eda6f0..3aadd3fb9e 100644 --- a/backend/utils/misc.py +++ b/backend/utils/misc.py @@ -97,18 +97,19 @@ def openai_chat_message_template(model: str): } -def openai_chat_chunk_message_template(model: str, message: str): +def openai_chat_chunk_message_template(model: str, message: str) -> dict: template = openai_chat_message_template(model) template["object"] = "chat.completion.chunk" template["choices"][0]["delta"] = {"content": message} return template -def openai_chat_completion_message_template(model: str, message: str): +def openai_chat_completion_message_template(model: str, message: str) -> dict: template = openai_chat_message_template(model) template["object"] = "chat.completion" template["choices"][0]["message"] = {"content": message, "role": "assistant"} template["choices"][0]["finish_reason"] = "stop" + return template def get_gravatar_url(email): From 080d45239a473c83817f78e818befe0aa3b631b7 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 14:27:18 +0200 Subject: [PATCH 30/81] fix: chat control "stop token" param issue --- src/lib/components/chat/Chat.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index a8e42279a7..253dada19b 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -803,8 +803,8 @@ ...(params ?? $settings.params ?? {}), stop: params?.stop ?? $settings?.params?.stop ?? undefined - ? (params?.stop ?? $settings.params.stop).map((str) => - decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) + ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map( + (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) ) : undefined, num_predict: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined, @@ -1103,8 +1103,8 @@ seed: params?.seed ?? $settings?.params?.seed ?? undefined, stop: params?.stop ?? $settings?.params?.stop ?? undefined - ? (params?.stop ?? $settings.params.stop).map((str) => - decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) + ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map( + (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) ) : undefined, temperature: params?.temperature ?? $settings?.params?.temperature ?? undefined, From 4441338574ae16e0d8c8f479385b5be2917d7bdc Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 15:58:36 +0200 Subject: [PATCH 31/81] refac: onScroll -> IntersectionObserver for infinite scroll --- src/lib/components/layout/Sidebar.svelte | 91 ++++++++++++++---------- src/lib/stores/index.ts | 1 + 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 0095a8fcd7..07dac20f01 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -39,7 +39,7 @@ import UserMenu from './Sidebar/UserMenu.svelte'; import ChatItem from './Sidebar/ChatItem.svelte'; import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; - import Sparkles from '../icons/Sparkles.svelte'; + import Spinner from '../common/Spinner.svelte'; const BREAKPOINT = 768; @@ -58,10 +58,8 @@ let paginationScrollThreashold = 0.6; let nextPageLoading = false; let chatPagniationComplete = false; - // number of chats per page depends on screen size. - // 35px is the height of each chat item. - // load 5 extra chats - pageLimit.set(Math.round(window.innerHeight / 35) + 5); + + pageLimit.set(20); $: filteredChatList = $chats.filter((chat) => { if (search === '') { @@ -153,6 +151,48 @@ window.addEventListener('focus', onFocus); window.addEventListener('blur', onBlur); + // Infinite scroll + const loader = document.getElementById('loader'); + + const observer = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + loadMoreContent(); + observer.unobserve(loader); // Stop observing until content is loaded + } + }); + }, + { + root: null, // viewport + rootMargin: '0px', + threshold: 1.0 // When 100% of the loader is visible + } + ); + + observer.observe(loader); + const loadMoreContent = async () => { + if (!$scrollPaginationEnabled) return; + if ($tagView) return; + if (nextPageLoading) return; + if (chatPagniationComplete) return; + + nextPageLoading = true; + pageSkip.set($pageSkip + 1); + // extend existing chats + const nextPageChats = await getChatList( + localStorage.token, + $pageSkip * $pageLimit, + $pageLimit + ); + // once the bottom of the list has been reached (no results) there is no need to continue querying + chatPagniationComplete = nextPageChats.length === 0; + await chats.set([...$chats, ...nextPageChats]); + nextPageLoading = false; + + observer.observe(loader); // Start observing again after content is loaded + }; + return () => { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); @@ -427,8 +467,8 @@ bind:value={search} on:focus={async () => { disablePagination(); + // TODO: migrate backend for more scalable search mechanism await chats.set(await getChatList(localStorage.token)); // when searching, load all chats - enrichChatsWithContent($chats); }} /> @@ -506,33 +546,7 @@
{/if} -
{ - if (!$scrollPaginationEnabled) return; - if ($tagView) return; - if (nextPageLoading) return; - if (chatPagniationComplete) return; - - const maxScroll = e.target.scrollHeight - e.target.clientHeight; - const currentPos = e.target.scrollTop; - const ratio = currentPos / maxScroll; - if (ratio >= paginationScrollThreashold) { - nextPageLoading = true; - pageSkip.set($pageSkip + 1); - // extend existing chats - const nextPageChats = await getChatList( - localStorage.token, - $pageSkip * $pageLimit, - $pageLimit - ); - // once the bottom of the list has been reached (no results) there is no need to continue querying - chatPagniationComplete = nextPageChats.length === 0; - await chats.set([...$chats, ...nextPageChats]); - nextPageLoading = false; - } - }} - > +
{#each filteredChatList as chat, idx} {#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
{/each} - {#if nextPageLoading} -
- + + {#if !chatPagniationComplete} +
+ +
Loading...
{/if}
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 6e14fcb2c1..b33046caa5 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -41,6 +41,7 @@ export const showSettings = writable(false); export const showArchivedChats = writable(false); export const showChangelog = writable(false); export const showCallOverlay = writable(false); + export const scrollPaginationEnabled = writable(true); export const pageSkip = writable(0); export const pageLimit = writable(-1); From 565f40c64250c69ec1f7a5a296c85f0d7dbd4a6b Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 4 Aug 2024 15:16:14 +0100 Subject: [PATCH 32/81] feat: add ENABLE_ADMIN_CHAT_ACCESS to control admin access to user chats --- backend/apps/webui/routers/chats.py | 9 +++++++-- backend/config.py | 4 ++++ backend/main.py | 2 ++ src/lib/stores/index.ts | 1 + src/routes/(app)/admin/+page.svelte | 2 +- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 80308a451b..26eefd4afe 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -28,7 +28,7 @@ from apps.webui.models.tags import ( from constants import ERROR_MESSAGES -from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT +from config import SRC_LOG_LEVELS, ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_CHAT_ACCESS log = logging.getLogger(__name__) log.setLevel(SRC_LOG_LEVELS["MODELS"]) @@ -81,6 +81,11 @@ async def get_user_chat_list_by_user_id( skip: int = 0, limit: int = 50, ): + if not ENABLE_ADMIN_CHAT_ACCESS: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) return Chats.get_chat_list_by_user_id( user_id, include_archived=True, skip=skip, limit=limit ) @@ -183,7 +188,7 @@ async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user)): if user.role == "user": chat = Chats.get_chat_by_share_id(share_id) - elif user.role == "admin": + elif user.role == "admin" and ENABLE_ADMIN_CHAT_ACCESS: chat = Chats.get_chat_by_id(share_id) if chat: diff --git a/backend/config.py b/backend/config.py index e976b226df..5e7d000c67 100644 --- a/backend/config.py +++ b/backend/config.py @@ -824,6 +824,10 @@ WEBHOOK_URL = PersistentConfig( ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true" +ENABLE_ADMIN_CHAT_ACCESS = ( + os.environ.get("ENABLE_ADMIN_CHAT_ACCESS", "True").lower() == "true" +) + ENABLE_COMMUNITY_SHARING = PersistentConfig( "ENABLE_COMMUNITY_SHARING", "ui.enable_community_sharing", diff --git a/backend/main.py b/backend/main.py index a7dd8bc23b..7a7efcd5f9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -116,6 +116,7 @@ from config import ( WEBUI_SECRET_KEY, WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE, + ENABLE_ADMIN_CHAT_ACCESS, AppConfig, ) @@ -2001,6 +2002,7 @@ async def get_app_config(): "enable_image_generation": images_app.state.config.ENABLED, "enable_community_sharing": webui_app.state.config.ENABLE_COMMUNITY_SHARING, "enable_admin_export": ENABLE_ADMIN_EXPORT, + "enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS, }, "audio": { "tts": { diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 1b0257c4b9..5874ce4569 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -149,6 +149,7 @@ type Config = { enable_web_search?: boolean; enable_image_generation: boolean; enable_admin_export: boolean; + enable_admin_chat_access: boolean; enable_community_sharing: boolean; }; oauth: { diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index 26375c9c3a..3387e27462 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -307,7 +307,7 @@
- {#if user.role !== 'admin'} + {#if $config.features.enable_admin_chat_access && user.role !== 'admin'}
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index b33046caa5..870cc55ff6 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -45,7 +45,6 @@ export const showCallOverlay = writable(false); export const scrollPaginationEnabled = writable(true); export const pageSkip = writable(0); export const pageLimit = writable(-1); -export const tagView = writable(false); export type Model = OpenAIModel | OllamaModel; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 471ecf9d4c..55e1a6e959 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import sha256 from 'js-sha256'; import { WEBUI_BASE_URL } from '$lib/constants'; -import { scrollPaginationEnabled, pageLimit, pageSkip } from '$lib/stores'; +import { scrollPaginationEnabled, pageLimit, pageSkip, chats } from '$lib/stores'; ////////////////////////// // Helper functions @@ -781,6 +781,13 @@ export const bestMatchingLanguage = (supportedLanguages, preferredLanguages, def return match || defaultLocale; }; +export const enablePagination = () => { + chats.set([]); + scrollPaginationEnabled.set(true); + pageLimit.set(20); + pageSkip.set(0); +}; + export const disablePagination = () => { scrollPaginationEnabled.set(false); pageLimit.set(-1); From a084938d9cabcf3a93492d2d48032b7d83cef9f1 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 16:58:08 +0200 Subject: [PATCH 34/81] refac: chatlist skip, limit -> page --- backend/apps/webui/routers/chats.py | 10 ++++- src/lib/apis/chats/index.ts | 9 +++- src/lib/components/chat/Chat.svelte | 42 +++++++++---------- src/lib/components/chat/Messages.svelte | 5 ++- src/lib/components/chat/Settings/Chats.svelte | 2 +- src/lib/components/chat/Tags.svelte | 7 ++-- src/lib/components/layout/Sidebar.svelte | 33 +++++++-------- .../components/layout/Sidebar/ChatItem.svelte | 26 +++++------- src/lib/stores/index.ts | 3 +- src/lib/utils/index.ts | 9 ++-- 10 files changed, 72 insertions(+), 74 deletions(-) diff --git a/backend/apps/webui/routers/chats.py b/backend/apps/webui/routers/chats.py index 47c7c4a87a..0b327d0a01 100644 --- a/backend/apps/webui/routers/chats.py +++ b/backend/apps/webui/routers/chats.py @@ -43,9 +43,15 @@ router = APIRouter() @router.get("/", response_model=List[ChatTitleIdResponse]) @router.get("/list", response_model=List[ChatTitleIdResponse]) async def get_session_user_chat_list( - user=Depends(get_verified_user), skip: int = 0, limit: int = -1 + user=Depends(get_verified_user), page: Optional[int] = None ): - return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit) + if page: + limit = 20 + skip = (page - 1) * limit + + return Chats.get_chat_title_id_list_by_user_id(user.id, skip=skip, limit=limit) + else: + return Chats.get_chat_title_id_list_by_user_id(user.id) ############################ diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 8ff12f4b68..8f4f81aea4 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -32,10 +32,15 @@ export const createNewChat = async (token: string, chat: object) => { return res; }; -export const getChatList = async (token: string = '', skip: number = 0, limit: number = -1) => { +export const getChatList = async (token: string = '', page: number | null = null) => { let error = null; + const searchParams = new URLSearchParams(); - const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?skip=${skip}&limit=${limit}`, { + if (page !== null) { + searchParams.append('page', `${page}`); + } + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/?${searchParams.toString()}`, { method: 'GET', headers: { Accept: 'application/json', diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 49cecb6961..c55605f82b 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -26,8 +26,7 @@ socket, showCallOverlay, tools, - pageSkip, - pageLimit + currentChatPage } from '$lib/stores'; import { convertMessagesToHistory, @@ -423,9 +422,9 @@ params: params, files: chatFiles }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); } } }; @@ -471,9 +470,9 @@ params: params, files: chatFiles }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); } } }; @@ -633,9 +632,9 @@ tags: [], timestamp: Date.now() }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); await chatId.set(chat.id); } else { await chatId.set('local'); @@ -711,7 +710,8 @@ }) ); - await chats.set(await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit)); + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); return _responses; }; @@ -958,9 +958,9 @@ params: params, files: chatFiles }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); } } } else { @@ -1227,9 +1227,9 @@ params: params, files: chatFiles }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); } } } else { @@ -1394,9 +1394,9 @@ if ($settings.saveChatHistory ?? true) { chat = await updateChatById(localStorage.token, _chatId, { title: _title }); - await chats.set( - await getChatList(localStorage.token, 0, $pageSkip * $pageLimit || $pageLimit) - ); + + currentChatPage.set(0); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); } }; diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 8e2946c196..5ba9fefaaa 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -1,6 +1,6 @@ - + +
+ +
From 5ce15bb0df3d48f0cf55d2c9660e563a411d8b77 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 18:16:00 +0200 Subject: [PATCH 42/81] refac --- src/lib/components/common/ImagePreview.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/common/ImagePreview.svelte b/src/lib/components/common/ImagePreview.svelte index c1d1fef6b1..5efc704420 100644 --- a/src/lib/components/common/ImagePreview.svelte +++ b/src/lib/components/common/ImagePreview.svelte @@ -54,7 +54,7 @@ bind:this={previewElement} class="modal fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain" > -
+
- +
{/if} From d8c39569be1f4bc907cfca81614778ade265a5ee Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 18:20:59 +0200 Subject: [PATCH 43/81] fix: infinite scroll stuck on loading issue --- src/lib/components/common/Loader.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/common/Loader.svelte b/src/lib/components/common/Loader.svelte index dbe84fd603..86c415ae4f 100644 --- a/src/lib/components/common/Loader.svelte +++ b/src/lib/components/common/Loader.svelte @@ -17,7 +17,7 @@ { root: null, // viewport rootMargin: '0px', - threshold: 1.0 // When 100% of the loader is visible + threshold: 0.1 // When 10% of the loader is visible } ); From b414fde6ca976875b4caca32a2549c823bfaa711 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 4 Aug 2024 18:34:29 +0200 Subject: [PATCH 44/81] fix --- src/lib/components/chat/Messages/ResponseMessage.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 4f01c24c8b..8cd377bdf2 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -533,7 +533,7 @@ {#if inlineToken.type === 'image'} {inlineToken.text} {:else} - {@html marked.parseInline(inlineToken.raw, { + {@html marked.parse(inlineToken.raw, { ...defaults, gfm: true, breaks: true, @@ -542,7 +542,7 @@ {/if} {/each} {:else} - {@html marked.parseInline(token.raw, { + {@html marked.parse(token.raw, { ...defaults, gfm: true, breaks: true, From b217cb76f0926b85c4452237ed6b827924739e0b Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Mon, 5 Aug 2024 01:06:52 +0800 Subject: [PATCH 45/81] i18n: Update and improve zh-TW Traditional Chinese translation --- src/lib/i18n/locales/zh-TW/translation.json | 679 ++++++++++---------- 1 file changed, 340 insertions(+), 339 deletions(-) diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index bc91e49649..867bacae93 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -1,25 +1,25 @@ { - "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示無期限。", - "(Beta)": "(測試版)", - "(e.g. `sh webui.sh --api --api-auth username_password`)": "", - "(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)", - "(latest)": "(最新版)", + "'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s'、'm'、'h'、'd'、'w' 或 '-1' 表示無到期時間。", + "(Beta)": "(測試版)", + "(e.g. `sh webui.sh --api --api-auth username_password`)": "(例如 `sh webui.sh --api --api-auth username_password`)", + "(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)", + "(latest)": "(最新版)", "{{ models }}": "{{ models }}", "{{ owner }}: You cannot delete a base model": "{{ owner }}:您無法刪除基礎模型", "{{modelName}} is thinking...": "{{modelName}} 正在思考...", - "{{user}}'s Chats": "{{user}} 的聊天", + "{{user}}'s Chats": "{{user}} 的對話", "{{webUIName}} Backend Required": "需要 {{webUIName}} 後端", - "A task model is used when performing tasks such as generating titles for chats and web search queries": "在執行任務時使用任務模型,例如為聊天和網頁搜尋查詢生成標題", - "a user": "使用者", + "A task model is used when performing tasks such as generating titles for chats and web search queries": "執行產生對話標題和網頁搜尋查詢等任務時會使用任務模型", + "a user": "一位使用者", "About": "關於", "Account": "帳號", - "Account Activation Pending": "帳號啟用中", - "Accurate information": "準確資訊", - "Actions": "", + "Account Activation Pending": "帳號待啟用", + "Accurate information": "正確資訊", + "Actions": "動作", "Active Users": "活躍使用者", "Add": "新增", "Add a model id": "新增模型 ID", - "Add a short description about what this model does": "為這個模型新增一個簡短描述", + "Add a short description about what this model does": "新增這個模型的簡短描述", "Add a short title for this prompt": "為這個提示詞新增一個簡短的標題", "Add a tag": "新增標籤", "Add custom prompt": "新增自訂提示詞", @@ -28,131 +28,131 @@ "Add Memory": "新增記憶", "Add message": "新增訊息", "Add Model": "新增模型", - "Add Tag": "", + "Add Tag": "新增標籤", "Add Tags": "新增標籤", "Add User": "新增使用者", - "Adjusting these settings will apply changes universally to all users.": "調整這些設定將對所有使用者進行更改。", + "Adjusting these settings will apply changes universally to all users.": "調整這些設定將會全面套用到所有使用者。", "admin": "管理員", "Admin": "管理員", "Admin Panel": "管理員控制台", - "Admin Settings": "管理設定", - "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理員隨時可以使用所有工具;使用者需要在工作區中為每個模型分配工具。", + "Admin Settings": "管理員設定", + "Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "管理員可以隨時使用所有工具;使用者需要在工作區中為每個模型分配工具。", "Advanced Parameters": "進階參數", "Advanced Params": "進階參數", - "all": "所有", + "all": "全部", "All Documents": "所有文件", "All Users": "所有使用者", "Allow": "允許", - "Allow Chat Deletion": "允許刪除聊天紀錄", + "Allow Chat Deletion": "允許刪除對話紀錄", "Allow non-local voices": "允許非本機語音", "Allow User Location": "允許使用者位置", - "Allow Voice Interruption in Call": "", - "alphanumeric characters and hyphens": "英文字母、數字(0~9)和連字元(-)", + "Allow Voice Interruption in Call": "允許在通話中打斷語音", + "alphanumeric characters and hyphens": "英文字母、數字和連字號", "Already have an account?": "已經有帳號了嗎?", - "an assistant": "助手", + "an assistant": "一位助手", "and": "和", - "and create a new shared link.": "並建立一個新的共享連結。", - "API Base URL": "API 基本 URL", + "and create a new shared link.": "並建立新的共用連結。", + "API Base URL": "API 基礎 URL", "API Key": "API 金鑰", "API Key created.": "API 金鑰已建立。", "API keys": "API 金鑰", "April": "4 月", "Archive": "封存", - "Archive All Chats": "封存所有聊天紀錄", - "Archived Chats": "已封存的聊天紀錄", - "are allowed - Activate this command by typing": "是允許的 - 透過輸入", + "Archive All Chats": "封存所有對話紀錄", + "Archived Chats": "封存的對話紀錄", + "are allowed - Activate this command by typing": "已允許 - 輸入此命令來啟用", "Are you sure?": "您確定嗎?", "Attach file": "附加檔案", - "Attention to detail": "細節精確", + "Attention to detail": "注重細節", "Audio": "音訊", - "Audio settings updated successfully": "", + "Audio settings updated successfully": "成功更新音訊設定", "August": "8 月", - "Auto-playback response": "自動播放回答", - "AUTOMATIC1111 Api Auth String": "", - "AUTOMATIC1111 Base URL": "AUTOMATIC1111 基本 URL", - "AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基本 URL", + "Auto-playback response": "自動播放回應", + "AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 API 驗證字串", + "AUTOMATIC1111 Base URL": "AUTOMATIC1111 基礎 URL", + "AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基礎 URL。", "available!": "可用!", "Back": "返回", "Bad Response": "錯誤回應", "Banners": "橫幅", "Base Model (From)": "基礎模型(來自)", "Batch Size (num_batch)": "批次大小(num_batch)", - "before": "前", - "Being lazy": "懶人模式", + "before": "之前", + "Being lazy": "懶惰模式", "Brave Search API Key": "Brave 搜尋 API 金鑰", - "Bypass SSL verification for Websites": "跳過網站的 SSL 驗證", - "Call": "呼叫", - "Call feature is not supported when using Web STT engine": "使用 Web STT 引擎時不支援呼叫功能", + "Bypass SSL verification for Websites": "略過網站的 SSL 驗證", + "Call": "通話", + "Call feature is not supported when using Web STT engine": "使用網頁語音辨識 (Web STT) 引擎時不支援通話功能", "Camera": "相機", "Cancel": "取消", "Capabilities": "功能", "Change Password": "修改密碼", - "Chat": "聊天", - "Chat Background Image": "", - "Chat Bubble UI": "聊天氣泡介面", - "Chat Controls": "", - "Chat direction": "聊天方向", - "Chat History": "聊天紀錄", - "Chat History is off for this browser.": "此瀏覽器已關閉聊天紀錄。", - "Chats": "聊天", - "Check Again": "重新檢查", + "Chat": "對話", + "Chat Background Image": "對話背景圖片", + "Chat Bubble UI": "對話氣泡介面", + "Chat Controls": "對話控制項", + "Chat direction": "對話方向", + "Chat History": "對話紀錄", + "Chat History is off for this browser.": "此瀏覽器已關閉對話紀錄。", + "Chats": "對話", + "Check Again": "再次檢查", "Check for updates": "檢查更新", "Checking for updates...": "正在檢查更新...", - "Choose a model before saving...": "儲存前選擇一個模型...", + "Choose a model before saving...": "儲存前請選擇一個模型...", "Chunk Overlap": "區塊重疊", "Chunk Params": "區塊參數", "Chunk Size": "區塊大小", - "Citation": "引文", + "Citation": "引用", "Clear memory": "清除記憶", - "Click here for help.": "點選這裡尋求幫助。", + "Click here for help.": "點選這裡取得協助。", "Click here to": "點選這裡", - "Click here to download user import template file.": "點選這裡下載使用者匯入的範本", + "Click here to download user import template file.": "點選這裡下載使用者匯入範本檔案。", "Click here to select": "點選這裡選擇", - "Click here to select a csv file.": "點選這裡選擇 csv 檔案。", - "Click here to select a py file.": "點選這裡選擇 py 檔案。", + "Click here to select a csv file.": "點選這裡選擇 CSV 檔案。", + "Click here to select a py file.": "點選這裡選擇 Python 檔案。", "Click here to select documents.": "點選這裡選擇文件。", "click here.": "點選這裡。", - "Click on the user role button to change a user's role.": "點選使用者角色按鈕以更改使用者的角色。", - "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "", + "Click on the user role button to change a user's role.": "點選使用者角色按鈕以變更使用者的角色。", + "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "剪貼簿寫入權限遭拒。請檢查您的瀏覽器設定,以授予必要的存取權限。", "Clone": "複製", "Close": "關閉", - "Code formatted successfully": "", + "Code formatted successfully": "成功格式化程式碼", "Collection": "收藏", "ComfyUI": "ComfyUI", - "ComfyUI Base URL": "ComfyUI 基本 URL", - "ComfyUI Base URL is required.": "需要 ComfyUI 基本 URL", + "ComfyUI Base URL": "ComfyUI 基礎 URL", + "ComfyUI Base URL is required.": "需要 ComfyUI 基礎 URL。", "Command": "命令", - "Concurrent Requests": "同時請求", - "Confirm": "", + "Concurrent Requests": "平行請求", + "Confirm": "確認", "Confirm Password": "確認密碼", - "Confirm your action": "", + "Confirm your action": "確認您的動作", "Connections": "連線", - "Contact Admin for WebUI Access": "聯絡管理員以取得 WebUI 存取權", + "Contact Admin for WebUI Access": "請聯絡管理員以取得 WebUI 存取權限", "Content": "內容", - "Content Extraction": "", + "Content Extraction": "內容提取", "Context Length": "上下文長度", - "Continue Response": "繼續回答", - "Continue with {{provider}}": "", - "Controls": "", - "Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!", + "Continue Response": "繼續回應", + "Continue with {{provider}}": "使用 {{provider}} 繼續", + "Controls": "控制項", + "Copied shared chat URL to clipboard!": "已複製共用對話 URL 到剪貼簿!", "Copy": "複製", "Copy last code block": "複製最後一個程式碼區塊", - "Copy last response": "複製最後一個回答", + "Copy last response": "複製最後一個回應", "Copy Link": "複製連結", "Copying to clipboard was successful!": "成功複製到剪貼簿!", "Create a model": "建立模型", "Create Account": "建立帳號", - "Create new key": "建立新金鑰", - "Create new secret key": "建立新金鑰", + "Create new key": "建立新的金鑰", + "Create new secret key": "建立新的金鑰", "Created at": "建立於", "Created At": "建立於", - "Created by": "", - "CSV Import": "", + "Created by": "建立者", + "CSV Import": "CSV 匯入", "Current Model": "目前模型", "Current Password": "目前密碼", "Custom": "自訂", - "Customize models for a specific purpose": "為特定目的自訂模型", - "Dark": "暗色", + "Customize models for a specific purpose": "自訂模型以用於特定目的", + "Dark": "深色", "Dashboard": "儀表板", "Database": "資料庫", "December": "12 月", @@ -165,107 +165,107 @@ "Default User Role": "預設使用者角色", "delete": "刪除", "Delete": "刪除", - "Delete a model": "刪除一個模型", - "Delete All Chats": "刪除所有聊天紀錄", - "Delete chat": "刪除聊天紀錄", - "Delete Chat": "刪除聊天紀錄", - "Delete chat?": "", - "Delete Doc": "", - "Delete function?": "", - "Delete prompt?": "", + "Delete a model": "刪除模型", + "Delete All Chats": "刪除所有對話紀錄", + "Delete chat": "刪除對話紀錄", + "Delete Chat": "刪除對話紀錄", + "Delete chat?": "刪除對話紀錄?", + "Delete Doc": "刪除文件", + "Delete function?": "刪除函式?", + "Delete prompt?": "刪除提示詞?", "delete this link": "刪除此連結", - "Delete tool?": "", + "Delete tool?": "刪除工具?", "Delete User": "刪除使用者", "Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}", "Deleted {{name}}": "已刪除 {{name}}", "Description": "描述", "Didn't fully follow instructions": "未完全遵循指示", - "Disabled": "", - "Discover a function": "", - "Discover a model": "發現新模型", - "Discover a prompt": "發現新提示詞", - "Discover a tool": "", - "Discover, download, and explore custom functions": "", - "Discover, download, and explore custom prompts": "發現、下載並探索自訂提示詞", - "Discover, download, and explore custom tools": "", - "Discover, download, and explore model presets": "發現、下載並探索模型預設值", + "Disabled": "已停用", + "Discover a function": "發掘函式", + "Discover a model": "發掘模型", + "Discover a prompt": "發掘提示詞", + "Discover a tool": "發掘工具", + "Discover, download, and explore custom functions": "發掘、下載及探索自訂函式", + "Discover, download, and explore custom prompts": "發掘、下載及探索自訂提示詞", + "Discover, download, and explore custom tools": "發掘、下載及探索自訂工具", + "Discover, download, and explore model presets": "發掘、下載及探索模型預設集", "Dismissible": "可忽略", - "Display Emoji in Call": "在呼叫中顯示表情符號", - "Display the username instead of You in the Chat": "在聊天中顯示使用者名稱而不是「您」", - "Do not install functions from sources you do not fully trust.": "", - "Do not install tools from sources you do not fully trust.": "", + "Display Emoji in Call": "在通話中顯示表情符號", + "Display the username instead of You in the Chat": "在對話中顯示使用者名稱,而非「您」", + "Do not install functions from sources you do not fully trust.": "請勿從您無法完全信任的來源安裝函式。", + "Do not install tools from sources you do not fully trust.": "請勿從您無法完全信任的來源安裝工具。", "Document": "文件", "Document Settings": "文件設定", "Documentation": "文件", "Documents": "文件", - "does not make any external connections, and your data stays securely on your locally hosted server.": "不會與外部連線,您的資料會安全地留在您的本機伺服器上。", + "does not make any external connections, and your data stays securely on your locally hosted server.": "不會建立任何外部連線,而且您的資料會安全地儲存在您本機伺服器上。", "Don't Allow": "不允許", - "Don't have an account?": "還沒有註冊帳號?", - "don't install random functions from sources you don't trust.": "", - "don't install random tools from sources you don't trust.": "", - "Don't like the style": "不喜歡這個樣式?", - "Done": "", + "Don't have an account?": "還沒註冊帳號嗎?", + "don't install random functions from sources you don't trust.": "請勿從您無法信任的來源安裝隨機函式。", + "don't install random tools from sources you don't trust.": "請勿從您無法信任的來源安裝隨機工具。", + "Don't like the style": "不喜歡這個樣式", + "Done": "完成", "Download": "下載", - "Download canceled": "下載已取消", + "Download canceled": "已取消下載", "Download Database": "下載資料庫", "Drop any files here to add to the conversation": "拖拽任意檔案到此處以新增至對話", - "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如 '30s', '10m'。有效的時間單位為 's', 'm', 'h'。", + "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如:'30s'、'10m'。有效的時間單位為 's'、'm'、'h'。", "Edit": "編輯", "Edit Doc": "編輯文件", "Edit Memory": "編輯記憶", "Edit User": "編輯使用者", - "ElevenLabs": "", + "ElevenLabs": "ElevenLabs", "Email": "電子郵件", "Embedding Batch Size": "嵌入批次大小", "Embedding Model": "嵌入模型", "Embedding Model Engine": "嵌入模型引擎", "Embedding model set to \"{{embedding_model}}\"": "嵌入模型已設定為 \"{{embedding_model}}\"", - "Enable Chat History": "啟用聊天紀錄", + "Enable Chat History": "啟用對話紀錄", "Enable Community Sharing": "啟用社群分享", - "Enable New Sign Ups": "允許註冊新帳號", + "Enable New Sign Ups": "允許新使用者註冊", "Enable Web Search": "啟用網頁搜尋", - "Enabled": "", - "Engine": "", - "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "請確保您的 CSV 檔案包含這四個欄位,並按照此順序:名稱、電子郵件、密碼、角色。", - "Enter {{role}} message here": "在這裡輸入 {{role}} 訊息", - "Enter a detail about yourself for your LLMs to recall": "輸入 LLM 記憶的詳細內容", - "Enter api auth string (e.g. username:password)": "", + "Enabled": "已啟用", + "Engine": "引擎", + "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "請確認您的 CSV 檔案包含以下 4 個欄位,並按照此順序排列:名稱、電子郵件、密碼、角色。", + "Enter {{role}} message here": "在此輸入 {{role}} 訊息", + "Enter a detail about yourself for your LLMs to recall": "輸入有關您的詳細資訊,讓您的大型語言模型可以回想起來", + "Enter api auth string (e.g. username:password)": "輸入 API 驗證字串(例如:username:password)", "Enter Brave Search API Key": "輸入 Brave 搜尋 API 金鑰", "Enter Chunk Overlap": "輸入區塊重疊", "Enter Chunk Size": "輸入區塊大小", - "Enter Github Raw URL": "輸入 Github Raw URL", + "Enter Github Raw URL": "輸入 GitHub Raw URL", "Enter Google PSE API Key": "輸入 Google PSE API 金鑰", "Enter Google PSE Engine Id": "輸入 Google PSE 引擎 ID", - "Enter Image Size (e.g. 512x512)": "輸入圖片大小(例如 512x512)", + "Enter Image Size (e.g. 512x512)": "輸入圖片大小(例如:512x512)", "Enter language codes": "輸入語言代碼", - "Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如 {{modelTag}})", - "Enter Number of Steps (e.g. 50)": "輸入步數(例如 50)", + "Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如:{{modelTag}})", + "Enter Number of Steps (e.g. 50)": "輸入步驟數(例如:50)", "Enter Score": "輸入分數", - "Enter Searxng Query URL": "輸入 Searxng 查詢 URL", + "Enter Searxng Query URL": "輸入 SearXNG 查詢 URL", "Enter Serper API Key": "輸入 Serper API 金鑰", "Enter Serply API Key": "輸入 Serply API 金鑰", "Enter Serpstack API Key": "輸入 Serpstack API 金鑰", "Enter stop sequence": "輸入停止序列", - "Enter system prompt": "", + "Enter system prompt": "輸入系統提示詞", "Enter Tavily API Key": "輸入 Tavily API 金鑰", - "Enter Tika Server URL": "", - "Enter Top K": "輸入 Top K", - "Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL(例如 http://127.0.0.1:7860/)", - "Enter URL (e.g. http://localhost:11434)": "輸入 URL(例如 http://localhost:11434)", + "Enter Tika Server URL": "輸入 Tika 伺服器 URL", + "Enter Top K": "輸入 Top K 值", + "Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL(例如:http://127.0.0.1:7860/)", + "Enter URL (e.g. http://localhost:11434)": "輸入 URL(例如:http://localhost:11434)", "Enter Your Email": "輸入您的電子郵件", "Enter Your Full Name": "輸入您的全名", - "Enter your message": "", + "Enter your message": "輸入您的訊息", "Enter Your Password": "輸入您的密碼", "Enter Your Role": "輸入您的角色", "Error": "錯誤", "Experimental": "實驗性功能", "Export": "匯出", - "Export All Chats (All Users)": "匯出所有聊天紀錄(所有使用者)", - "Export chat (.json)": "匯出聊天紀錄(.json)", - "Export Chats": "匯出聊天紀錄", - "Export Documents Mapping": "匯出文件對映", - "Export Functions": "匯出功能", - "Export LiteLLM config.yaml": "", + "Export All Chats (All Users)": "匯出所有對話紀錄(所有使用者)", + "Export chat (.json)": "匯出對話紀錄(.json)", + "Export Chats": "匯出對話紀錄", + "Export Documents Mapping": "匯出文件對應", + "Export Functions": "匯出函式", + "Export LiteLLM config.yaml": "匯出 LiteLLM config.yaml", "Export Models": "匯出模型", "Export Prompts": "匯出提示詞", "Export Tools": "匯出工具", @@ -274,153 +274,153 @@ "Failed to read clipboard contents": "無法讀取剪貼簿內容", "Failed to update settings": "無法更新設定", "February": "2 月", - "Feel free to add specific details": "請隨意新增詳細內容。", + "Feel free to add specific details": "歡迎自由新增特定細節", "File": "檔案", "File Mode": "檔案模式", "File not found.": "找不到檔案。", - "Files": "", - "Filter is now globally disabled": "", - "Filter is now globally enabled": "", + "Files": "檔案", + "Filter is now globally disabled": "篩選器現在已全域停用", + "Filter is now globally enabled": "篩選器現在已全域啟用", "Filters": "篩選器", - "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "偽造偵測:無法使用初始頭像。預設為預設個人影象。", - "Fluidly stream large external response chunks": "流暢地傳輸大型外部回應區塊", - "Focus chat input": "聚焦聊天輸入框", + "Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "偵測到指紋偽造:無法使用姓名縮寫作為大頭貼。將預設為預設個人檔案圖片。", + "Fluidly stream large external response chunks": "流暢地串流大型外部回應區塊", + "Focus chat input": "聚焦對話輸入", "Followed instructions perfectly": "完全遵循指示", "Form": "表單", - "Format your variables using square brackets like this:": "像這樣使用方括號來格式化您的變數:", + "Format your variables using square brackets like this:": "使用方括號來格式化您的變數,如下所示:", "Frequency Penalty": "頻率懲罰", - "Function created successfully": "", - "Function deleted successfully": "", - "Function Description (e.g. A filter to remove profanity from text)": "", - "Function ID (e.g. my_filter)": "", - "Function is now globally disabled": "", - "Function is now globally enabled": "", - "Function Name (e.g. My Filter)": "", - "Function updated successfully": "", - "Functions": "功能", - "Functions allow arbitrary code execution": "", - "Functions allow arbitrary code execution.": "", - "Functions imported successfully": "", - "General": "常用", - "General Settings": "常用設定", - "Generate Image": "生成圖片", - "Generating search query": "生成搜尋查詢", + "Function created successfully": "成功建立函式", + "Function deleted successfully": "成功刪除函式", + "Function Description (e.g. A filter to remove profanity from text)": "函式描述(例如:用於從文字中移除不雅詞彙的篩選器)", + "Function ID (e.g. my_filter)": "函式 ID(例如:my_filter)", + "Function is now globally disabled": "現在已在全域停用函式", + "Function is now globally enabled": "現在已在全域啟用函式", + "Function Name (e.g. My Filter)": "函式名稱(例如:我的篩選器)", + "Function updated successfully": "成功更新函式", + "Functions": "函式", + "Functions allow arbitrary code execution": "函式允許執行任意程式碼", + "Functions allow arbitrary code execution.": "函式允許執行任意程式碼。", + "Functions imported successfully": "成功匯入函式", + "General": "一般", + "General Settings": "一般設定", + "Generate Image": "產生圖片", + "Generating search query": "正在產生搜尋查詢", "Generation Info": "生成資訊", - "Get up and running with": "", - "Global": "", - "Good Response": "優秀的回應", + "Get up and running with": "開始使用", + "Global": "全域", + "Good Response": "良好回應", "Google PSE API Key": "Google PSE API 金鑰", "Google PSE Engine Id": "Google PSE 引擎 ID", "h:mm a": "h:mm a", - "has no conversations.": "沒有對話", + "has no conversations.": "沒有對話。", "Hello, {{name}}": "您好,{{name}}", - "Help": "幫助", + "Help": "說明", "Hide": "隱藏", "Hide Model": "隱藏模型", - "How can I help you today?": "今天能為您做些什麼?", + "How can I help you today?": "今天我能為您做些什麼?", "Hybrid Search": "混合搜尋", - "I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "", - "Image Generation (Experimental)": "影像生成(實驗性功能)", - "Image Generation Engine": "影像生成引擎", + "I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "我確認已閱讀並理解我的操作所帶來的影響。我了解執行任意程式碼的相關風險,並已驗證來源的可信度。", + "Image Generation (Experimental)": "圖片生成(實驗性功能)", + "Image Generation Engine": "圖片生成引擎", "Image Settings": "圖片設定", "Images": "圖片", - "Import Chats": "匯入聊天紀錄", - "Import Documents Mapping": "匯入文件對映", - "Import Functions": "匯入功能", + "Import Chats": "匯入對話紀錄", + "Import Documents Mapping": "匯入文件對應", + "Import Functions": "匯入函式", "Import Models": "匯入模型", "Import Prompts": "匯入提示詞", "Import Tools": "匯入工具", - "Include `--api-auth` flag when running stable-diffusion-webui": "", - "Include `--api` flag when running stable-diffusion-webui": "在執行 stable-diffusion-webui 時加上 `--api` 標誌", + "Include `--api-auth` flag when running stable-diffusion-webui": "執行 stable-diffusion-webui 時包含 `--api-auth` 參數", + "Include `--api` flag when running stable-diffusion-webui": "執行 stable-diffusion-webui 時包含 `--api` 參數", "Info": "資訊", "Input commands": "輸入命令", - "Install from Github URL": "從 Github URL 安裝", + "Install from Github URL": "從 GitHub URL 安裝", "Instant Auto-Send After Voice Transcription": "語音轉錄後立即自動傳送", "Interface": "介面", "Invalid Tag": "無效標籤", "January": "1 月", - "join our Discord for help.": "加入我們的 Discord 尋求幫助。", + "join our Discord for help.": "加入我們的 Discord 以尋求協助。", "JSON": "JSON", "JSON Preview": "JSON 預覽", "July": "7 月", "June": "6 月", "JWT Expiration": "JWT 過期時間", "JWT Token": "JWT Token", - "Keep Alive": "保持活躍", - "Keyboard shortcuts": "鍵盤快速鍵", + "Keep Alive": "保持連線", + "Keyboard shortcuts": "鍵盤快捷鍵", "Knowledge": "知識", "Language": "語言", - "large language models, locally.": "", - "Last Active": "最後活動", - "Last Modified": "最後修改", - "Light": "亮色", + "large language models, locally.": "在本機執行大型語言模型。", + "Last Active": "上次活動時間", + "Last Modified": "上次修改時間", + "Light": "淺色", "Listening...": "正在聆聽...", - "LLMs can make mistakes. Verify important information.": "LLM 可能會產生錯誤。請驗證重要資訊。", + "LLMs can make mistakes. Verify important information.": "大型語言模型可能會出錯。請驗證重要資訊。", "Local Models": "本機模型", - "LTR": "LTR", + "LTR": "從左到右", "Made by OpenWebUI Community": "由 OpenWebUI 社群製作", - "Make sure to enclose them with": "請確保變數有被以下符號框住:", + "Make sure to enclose them with": "請務必將它們放在", "Manage": "管理", "Manage Models": "管理模型", "Manage Ollama Models": "管理 Ollama 模型", "Manage Pipelines": "管理管線", "March": "3 月", - "Max Tokens (num_predict)": "最大 Token(num_predict)", - "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同時下載 3 個模型。請稍後再試。", + "Max Tokens (num_predict)": "最大 token 數(num_predict)", + "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可同時下載 3 個模型。請稍後再試。", "May": "5 月", - "Memories accessible by LLMs will be shown here.": "LLM 記憶將會顯示在此處。", + "Memories accessible by LLMs will be shown here.": "可被大型語言模型存取的記憶將顯示在這裡。", "Memory": "記憶", - "Memory added successfully": "", - "Memory cleared successfully": "", - "Memory deleted successfully": "", - "Memory updated successfully": "", - "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "建立連結後傳送的訊息將不會被共享。具有 URL 的使用者將會能夠檢視共享的聊天。", - "Min P": "", + "Memory added successfully": "成功新增記憶", + "Memory cleared successfully": "成功清除記憶", + "Memory deleted successfully": "成功刪除記憶", + "Memory updated successfully": "成功更新記憶", + "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "建立連結後傳送的訊息不會被分享。擁有網址的使用者將能夠檢視分享的對話內容。", + "Min P": "最小 P 值", "Minimum Score": "最低分數", "Mirostat": "Mirostat", "Mirostat Eta": "Mirostat Eta", "Mirostat Tau": "Mirostat Tau", - "MMMM DD, YYYY": "MMMM DD, YYYY", - "MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm", - "MMMM DD, YYYY hh:mm:ss A": "MMMM DD, YYYY hh:mm:ss A", - "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' 模型已成功下載。", - "Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' 模型已經在下載佇列中。", - "Model {{modelId}} not found": "找不到 {{modelId}} 模型", - "Model {{modelName}} is not vision capable": "{{modelName}} 模型不適用於視覺", - "Model {{name}} is now {{status}}": "{{name}} 模型現在是 {{status}}", - "Model created successfully!": "", - "Model filesystem path detected. Model shortname is required for update, cannot continue.": "已偵測到模型檔案系統路徑。需要更新模型簡稱,無法繼續。", + "MMMM DD, YYYY": "YYYY 年 MMMM DD 日", + "MMMM DD, YYYY HH:mm": "YYYY 年 MMMM DD 日 HH:mm", + "MMMM DD, YYYY hh:mm:ss A": "YYYY 年 MMMM DD 日 hh:mm:ss A", + "Model '{{modelName}}' has been successfully downloaded.": "模型「{{modelName}}」已成功下載。", + "Model '{{modelTag}}' is already in queue for downloading.": "模型「{{modelTag}}」已在下載佇列中。", + "Model {{modelId}} not found": "找不到模型 {{modelId}}", + "Model {{modelName}} is not vision capable": "模型 {{modelName}} 不具備視覺能力", + "Model {{name}} is now {{status}}": "模型 {{name}} 現在狀態為 {{status}}", + "Model created successfully!": "成功建立模型!", + "Model filesystem path detected. Model shortname is required for update, cannot continue.": "偵測到模型檔案系統路徑。更新需要模型簡稱,因此無法繼續。", "Model ID": "模型 ID", - "Model not selected": "未選擇模型", + "Model not selected": "未選取模型", "Model Params": "模型參數", - "Model updated successfully": "", - "Model Whitelisting": "白名單模型", + "Model updated successfully": "成功更新模型", + "Model Whitelisting": "模型白名單", "Model(s) Whitelisted": "模型已加入白名單", - "Modelfile Content": "Modelfile 內容", + "Modelfile Content": "模型檔案內容", "Models": "模型", "More": "更多", "Name": "名稱", "Name Tag": "名稱標籤", - "Name your model": "請輸入模型名稱", - "New Chat": "新增聊天", - "New Password": "新密碼", - "No content to speak": "", + "Name your model": "為您的模型命名", + "New Chat": "新增對話", + "New Password": "新的密碼", + "No content to speak": "沒有要朗讀的內容", "No documents found": "找不到文件", - "No file selected": "", - "No results found": "沒有找到結果", - "No search query generated": "沒有生成搜尋查詢", + "No file selected": "未選取檔案", + "No results found": "找不到任何結果", + "No search query generated": "未產生搜尋查詢", "No source available": "沒有可用的來源", - "No valves to update": "", + "No valves to update": "沒有要更新的閥門", "None": "無", "Not factually correct": "與真實資訊不符", - "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "註:如果設定最低分數,則搜尋將只返回分數大於或等於最低分數的文件。", + "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "注意:如果您設定了最低分數,則搜尋只會回傳分數大於或等於最低分數的文件。", "Notifications": "通知", "November": "11 月", - "num_thread (Ollama)": "num_thread(Ollama)", - "OAuth ID": "", + "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "OAuth ID", "October": "10 月", "Off": "關閉", - "Okay, Let's Go!": "好的,啟動吧!", + "Okay, Let's Go!": "好的,我們開始吧!", "OLED Dark": "OLED 深色", "Ollama": "Ollama", "Ollama API": "Ollama API", @@ -428,15 +428,15 @@ "Ollama API is disabled": "Ollama API 已停用", "Ollama Version": "Ollama 版本", "On": "開啟", - "Only": "僅有", - "Only alphanumeric characters and hyphens are allowed in the command string.": "命令字串中只能包含英文字母、數字(0~9)和連字元(-)。", - "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "哎呀!請稍等!您的文件還在處理中。我們正最佳化文件,請耐心等待,一旦準備好,我們會通知您。", - "Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!看起來 URL 無效。請仔細檢查後再試一次。", - "Oops! There was an error in the previous response. Please try again or contact admin.": "哎呀!先前的回應發生錯誤。請重試或聯絡管理員", - "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!您正在使用不支援的方法(僅有前端)。請從後端提供 WebUI。", + "Only": "僅限", + "Only alphanumeric characters and hyphens are allowed in the command string.": "命令字串中只允許使用英文字母、數字和連字號。", + "Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "哎呀!請稍等!您的檔案仍在處理中。我們正在完善它們。請耐心等待,我們會在它們準備好時通知您。", + "Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!這個 URL 似乎無效。請仔細檢查並再試一次。", + "Oops! There was an error in the previous response. Please try again or contact admin.": "哎呀!先前的回應發生錯誤。請重試或聯絡管理員。", + "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!您使用了不支援的方法(僅限前端)。請從後端提供 WebUI。", "Open AI (Dall-E)": "Open AI (Dall-E)", - "Open new chat": "開啟新聊天", - "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "", + "Open new chat": "開啟新的對話", + "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低於所需版本 (v{{REQUIRED_VERSION}})", "OpenAI": "OpenAI", "OpenAI API": "OpenAI API", "OpenAI API Config": "OpenAI API 設定", @@ -447,25 +447,25 @@ "Password": "密碼", "PDF document (.pdf)": "PDF 文件 (.pdf)", "PDF Extract Images (OCR)": "PDF 影像擷取(OCR 光學文字辨識)", - "pending": "待審查", - "Permission denied when accessing media devices": "存取媒體裝置時被拒絕權限", - "Permission denied when accessing microphone": "存取麥克風時被拒絕權限", - "Permission denied when accessing microphone: {{error}}": "存取麥克風時被拒絕權限:{{error}}", + "pending": "待處理", + "Permission denied when accessing media devices": "存取媒體裝置時權限遭拒", + "Permission denied when accessing microphone": "存取麥克風時權限遭拒", + "Permission denied when accessing microphone: {{error}}": "存取麥克風時權限遭拒:{{error}}", "Personalization": "個人化", - "Pin": "", - "Pinned": "", - "Pipeline deleted successfully": "", - "Pipeline downloaded successfully": "", + "Pin": "釘選", + "Pinned": "已釘選", + "Pipeline deleted successfully": "成功刪除管線", + "Pipeline downloaded successfully": "成功下載管線", "Pipelines": "管線", - "Pipelines Not Detected": "", + "Pipelines Not Detected": "未偵測到管線", "Pipelines Valves": "管線閥門", "Plain text (.txt)": "純文字 (.txt)", - "Playground": "AI 對話遊樂場", - "Please carefully review the following warnings:": "", - "Positive attitude": "積極態度", - "Previous 30 days": "前 30 天", - "Previous 7 days": "前 7 天", - "Profile Image": "個人影像", + "Playground": "遊樂場", + "Please carefully review the following warnings:": "請仔細閱讀以下警告:", + "Positive attitude": "積極的態度", + "Previous 30 days": "過去 30 天", + "Previous 7 days": "過去 7 天", + "Profile Image": "個人檔案圖片", "Prompt": "提示詞", "Prompt (e.g. Tell me a fun fact about the Roman Empire)": "提示詞(例如:告訴我關於羅馬帝國的一些趣事)", "Prompt Content": "提示詞內容", @@ -474,72 +474,73 @@ "Pull \"{{searchValue}}\" from Ollama.com": "從 Ollama.com 下載 \"{{searchValue}}\"", "Pull a model from Ollama.com": "從 Ollama.com 下載模型", "Query Params": "查詢參數", - "RAG Template": "RAG 範例", - "Read Aloud": "讀出", + "RAG Template": "RAG 範本", + "Read Aloud": "朗讀", "Record voice": "錄音", - "Redirecting you to OpenWebUI Community": "將您重新導向到 OpenWebUI 社群", - "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "將自己稱為「使用者」(例如,「使用者正在學習西班牙語」)", - "Refused when it shouldn't have": "不該拒絕時拒絕了", - "Regenerate": "重新生成", + "Redirecting you to OpenWebUI Community": "正在將您重新導向到 OpenWebUI 社群", + "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "以「使用者」稱呼自己(例如:「使用者正在學習西班牙文」)", + "Refused when it shouldn't have": "不應拒絕時拒絕了", + "Regenerate": "重新產生", "Release Notes": "發布說明", "Remove": "移除", "Remove Model": "移除模型", "Rename": "重新命名", - "Repeat Last N": "重複最後 N 次", + "Repeat Last N": "重複最後 N 個", "Request Mode": "請求模式", "Reranking Model": "重新排序模型", - "Reranking model disabled": "重新排序模型已停用", - "Reranking model set to \"{{reranking_model}}\"": "重新排序模型設定為 \"{{reranking_model}}\"", + "Reranking model disabled": "已停用重新排序模型", + "Reranking model set to \"{{reranking_model}}\"": "重新排序模型已設定為 \"{{reranking_model}}\"", "Reset": "重設", "Reset Upload Directory": "重設上傳目錄", "Reset Vector Storage": "重設向量儲存空間", - "Response AutoCopy to Clipboard": "自動複製回答到剪貼簿", - "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "", + "Response AutoCopy to Clipboard": "自動將回應複製到剪貼簿", + "Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "無法啟用回應通知,因為網站權限已遭拒。請前往瀏覽器設定以授予必要存取權限。", "Role": "角色", "Rosé Pine": "玫瑰松", "Rosé Pine Dawn": "黎明玫瑰松", - "RTL": "RTL", - "Run Llama 2, Code Llama, and other models. Customize and create your own.": "", + "RTL": "從右到左", + "Run Llama 2, Code Llama, and other models. Customize and create your own.": "執行 Llama 2、Code Llama 和其他模型。自訂並建立您自己的模型。", "Running": "運作中", "Save": "儲存", "Save & Create": "儲存並建立", "Save & Update": "儲存並更新", - "Save Tag": "", - "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": "現已不支援將聊天紀錄儲存到瀏覽器儲存空間中。請點選下面的按鈕下載並刪除您的聊天記錄。別擔心,您可以透過以下方式輕鬆地重新匯入您的聊天記錄到後端", + "Save Tag": "儲存標籤", + "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": "不再支援直接將對話記錄儲存到您的瀏覽器儲存空間。請點選下方按鈕來下載並刪除您的對話記錄。別擔心,您可以透過以下方式輕鬆地將對話記錄重新匯入後端", "Scan": "掃描", "Scan complete!": "掃描完成!", "Scan for documents from {{path}}": "從 {{path}} 掃描文件", - "Scroll to bottom when switching between branches": "", + "Scroll to bottom when switching between branches": "切換分支時捲動到底端", "Search": "搜尋", "Search a model": "搜尋模型", - "Search Chats": "搜尋聊天", + "Search Chats": "搜尋對話", "Search Documents": "搜尋文件", - "Search Functions": "搜尋功能", + "Search Functions": "搜尋函式", "Search Models": "搜尋模型", "Search Prompts": "搜尋提示詞", "Search Query Generation Prompt": "搜尋查詢生成提示詞", "Search Query Generation Prompt Length Threshold": "搜尋查詢生成提示詞長度閾值", "Search Result Count": "搜尋結果數量", "Search Tools": "搜尋工具", - "Searched {{count}} sites_other": "搜尋了 {{count}} 個網站", + "Searched {{count}} sites_one": "已搜尋 {{count}} 個網站", + "Searched {{count}} sites_other": "已搜尋 {{count}} 個網站", "Searching \"{{searchQuery}}\"": "正在搜尋 \"{{searchQuery}}\"", "Searxng Query URL": "Searxng 查詢 URL", - "See readme.md for instructions": "檢視 readme.md 取得指南", - "See what's new": "檢視最新內容", + "See readme.md for instructions": "檢視 readme.md 以取得說明", + "See what's new": "查看新功能", "Seed": "種子", "Select a base model": "選擇基礎模型", "Select a engine": "選擇引擎", - "Select a function": "", + "Select a function": "選擇函式", "Select a mode": "選擇模式", - "Select a model": "選擇一個模型", + "Select a model": "選擇模型", "Select a pipeline": "選擇管線", "Select a pipeline url": "選擇管線 URL", - "Select a tool": "", + "Select a tool": "選擇工具", "Select an Ollama instance": "選擇 Ollama 執行個體", "Select Documents": "選擇文件", "Select model": "選擇模型", "Select only one model to call": "僅選擇一個模型來呼叫", - "Selected model(s) do not support image inputs": "已選擇模型不支援影像輸入", + "Selected model(s) do not support image inputs": "選取的模型不支援圖片輸入", "Send": "傳送", "Send a Message": "傳送訊息", "Send message": "傳送訊息", @@ -547,7 +548,7 @@ "Serper API Key": "Serper API 金鑰", "Serply API Key": "Serply API 金鑰", "Serpstack API Key": "Serpstack API 金鑰", - "Server connection verified": "已驗證伺服器連線", + "Server connection verified": "伺服器連線已驗證", "Set as default": "設為預設", "Set Default Model": "設定預設模型", "Set embedding model (e.g. {{model}})": "設定嵌入模型(例如:{{model}})", @@ -557,35 +558,35 @@ "Set Task Model": "設定任務模型", "Set Voice": "設定語音", "Settings": "設定", - "Settings saved successfully!": "成功儲存設定", - "Settings updated successfully": "設定更新成功", + "Settings saved successfully!": "設定已成功儲存", + "Settings updated successfully": "設定已成功更新", "Share": "分享", - "Share Chat": "分享聊天", + "Share Chat": "分享對話", "Share to OpenWebUI Community": "分享到 OpenWebUI 社群", "short-summary": "簡短摘要", "Show": "顯示", "Show Admin Details in Account Pending Overlay": "在帳號待審覆蓋層中顯示管理員詳細資訊", "Show Model": "顯示模型", - "Show shortcuts": "顯示快速鍵", - "Show your support!": "", - "Showcased creativity": "展示創造性", + "Show shortcuts": "顯示快捷鍵", + "Show your support!": "表示您的支持!", + "Showcased creativity": "展現創意", "Sign in": "登入", "Sign Out": "登出", "Sign up": "註冊", "Signing in": "正在登入", "Source": "來源", - "Speech recognition error: {{error}}": "語音識別錯誤:{{error}}", - "Speech-to-Text Engine": "語音轉文字引擎", + "Speech recognition error: {{error}}": "語音辨識錯誤:{{error}}", + "Speech-to-Text Engine": "語音轉文字 (STT) 引擎", "Stop Sequence": "停止序列", - "STT Model": "STT 模型", - "STT Settings": "語音轉文字設定", + "STT Model": "語音轉文字 (STT) 模型", + "STT Settings": "語音轉文字 (STT) 設定", "Submit": "提交", "Subtitle (e.g. about the Roman Empire)": "副標題(例如:關於羅馬帝國)", "Success": "成功", "Successfully updated.": "更新成功。", "Suggested": "建議", - "Support": "", - "Support this plugin:": "", + "Support": "支援", + "Support this plugin:": "支持這個外掛:", "System": "系統", "System Prompt": "系統提示詞", "Tags": "標籤", @@ -595,94 +596,94 @@ "Temperature": "溫度", "Template": "範本", "Text Completion": "文字補全", - "Text-to-Speech Engine": "文字轉語音引擎", + "Text-to-Speech Engine": "文字轉語音 (TTS) 引擎", "Tfs Z": "Tfs Z", "Thanks for your feedback!": "感謝您的回饋!", - "The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "", - "The score should be a value between 0.0 (0%) and 1.0 (100%).": "分數應該介於 0.0(0%)和 1.0(100%)之間。", + "The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "這個外掛背後的開發者是來自社群的熱情志願者。如果您覺得這個外掛很有幫助,請考慮為其開發做出貢獻。", + "The score should be a value between 0.0 (0%) and 1.0 (100%).": "分數應該是介於 0.0(0%)和 1.0(100%)之間的值。", "Theme": "主題", "Thinking...": "正在思考...", - "This action cannot be undone. Do you wish to continue?": "此動作無法被復原。您想要繼續進行嗎?", - "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "這確保您寶貴的對話安全地儲存到您的後端資料庫。謝謝!", - "This is an experimental feature, it may not function as expected and is subject to change at any time.": "這是一個實驗性功能,可能無法如預期運作,並且隨時可能更改。", + "This action cannot be undone. Do you wish to continue?": "此操作無法復原。您確定要繼續進行嗎?", + "This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "這確保您寶貴的對話會安全地儲存到您的後端資料庫。謝謝!", + "This is an experimental feature, it may not function as expected and is subject to change at any time.": "這是一個實驗性功能,它有可能無法按預期運作,並且可能會隨時變更。", "This setting does not sync across browsers or devices.": "此設定不會在瀏覽器或裝置間同步。", - "This will delete": "", - "Thorough explanation": "詳細說明", - "Tika": "", - "Tika Server URL required.": "", - "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:透過在每次替換後在聊天輸入框中按 Tab 鍵連續更新多個變數。", + "This will delete": "這將會刪除", + "Thorough explanation": "詳細解釋", + "Tika": "Tika", + "Tika Server URL required.": "需要 Tika 伺服器 URL。", + "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替換後按下對話輸入框中的 Tab 鍵,即可連續更新多個變數欄位。", "Title": "標題", - "Title (e.g. Tell me a fun fact)": "標題(例如:告訴我一個有趣的事)", + "Title (e.g. Tell me a fun fact)": "標題(例如:告訴我一個有趣的事實)", "Title Auto-Generation": "自動產生標題", - "Title cannot be an empty string.": "標題不能為空字串", + "Title cannot be an empty string.": "標題不能是空字串。", "Title Generation Prompt": "自動產生標題的提示詞", "to": "到", - "To access the available model names for downloading,": "若想檢視可供下載的模型名稱,", - "To access the GGUF models available for downloading,": "若想檢視可供下載的 GGUF 模型名稱,", + "To access the available model names for downloading,": "若要存取可供下載的模型名稱,", + "To access the GGUF models available for downloading,": "若要存取可供下載的 GGUF 模型,", "To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "若要存取 WebUI,請聯絡管理員。管理員可以從管理面板管理使用者狀態。", "To add documents here, upload them to the \"Documents\" workspace first.": "若要在此新增文件,請先將它們上傳到「文件」工作區。", - "to chat input.": "到聊天輸入框來啟動此命令。", - "To select actions here, add them to the \"Functions\" workspace first.": "", - "To select filters here, add them to the \"Functions\" workspace first.": "若要在此選擇篩選器,請先將它們新增到「功能」工作區。", + "to chat input.": "到對話輸入。", + "To select actions here, add them to the \"Functions\" workspace first.": "若要在此選擇動作,請先將它們新增到「函式」工作區。", + "To select filters here, add them to the \"Functions\" workspace first.": "若要在此選擇篩選器,請先將它們新增到「函式」工作區。", "To select toolkits here, add them to the \"Tools\" workspace first.": "若要在此選擇工具包,請先將它們新增到「工具」工作區。", "Today": "今天", "Toggle settings": "切換設定", "Toggle sidebar": "切換側邊欄", - "Tokens To Keep On Context Refresh (num_keep)": "上下文重新整理時保留的 Token 數量(num_keep)", - "Tool created successfully": "", - "Tool deleted successfully": "", - "Tool imported successfully": "", - "Tool updated successfully": "", - "Toolkit Description (e.g. A toolkit for performing various operations)": "", - "Toolkit ID (e.g. my_toolkit)": "", - "Toolkit Name (e.g. My ToolKit)": "", + "Tokens To Keep On Context Refresh (num_keep)": "上下文重新整理時要保留的 token 數 (num_keep)", + "Tool created successfully": "成功建立工具", + "Tool deleted successfully": "成功刪除工具", + "Tool imported successfully": "成功匯入工具", + "Tool updated successfully": "成功更新工具", + "Toolkit Description (e.g. A toolkit for performing various operations)": "工具包描述(例如:用於執行各種操作的工具包)", + "Toolkit ID (e.g. my_toolkit)": "工具包 ID(例如:my_toolkit)", + "Toolkit Name (e.g. My ToolKit)": "工具包名稱(例如:我的工具包)", "Tools": "工具", - "Tools are a function calling system with arbitrary code execution": "", - "Tools have a function calling system that allows arbitrary code execution": "", - "Tools have a function calling system that allows arbitrary code execution.": "", + "Tools are a function calling system with arbitrary code execution": "工具是一個具有任意程式碼執行功能的函式呼叫系統", + "Tools have a function calling system that allows arbitrary code execution": "工具具有允許執行任意程式碼的函式呼叫系統", + "Tools have a function calling system that allows arbitrary code execution.": "工具具有允許執行任意程式碼的函式呼叫系統。", "Top K": "Top K", "Top P": "Top P", "Trouble accessing Ollama?": "存取 Ollama 時遇到問題?", - "TTS Model": "文字轉語音(TTS)模型", - "TTS Settings": "文字轉語音(TTS)設定", - "TTS Voice": "文字轉語音(TTS)聲調", + "TTS Model": "文字轉語音 (TTS) 模型", + "TTS Settings": "文字轉語音 (TTS) 設定", + "TTS Voice": "文字轉語音 (TTS) 聲音", "Type": "類型", - "Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 解析後的(下載)URL", + "Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 解析(下載)URL", "Uh-oh! There was an issue connecting to {{provider}}.": "哎呀!連線到 {{provider}} 時出現問題。", - "UI": "使用者界面", - "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "未知的檔案類型 '{{file_type}}'。但仍會繼續上傳。", - "Unpin": "", + "UI": "使用者介面", + "Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "未知的檔案類型 '{{file_type}}'。仍然繼續上傳檔案。", + "Unpin": "取消釘選", "Update": "更新", "Update and Copy Link": "更新並複製連結", "Update password": "更新密碼", "Updated at": "更新於", "Upload": "上傳", - "Upload a GGUF model": "上傳一個 GGUF 模型", + "Upload a GGUF model": "上傳 GGUF 模型", "Upload Files": "上傳檔案", "Upload Pipeline": "上傳管線", "Upload Progress": "上傳進度", "URL Mode": "URL 模式", - "Use '#' in the prompt input to load and select your documents.": "在輸入框中輸入 '#' 以載入並選擇您的文件。", + "Use '#' in the prompt input to load and select your documents.": "在提示詞輸入中使用 '#' 來載入和選擇您的文件。", "Use Gravatar": "使用 Gravatar", - "Use Initials": "使用初始頭像", - "use_mlock (Ollama)": "use_mlock(Ollama)", - "use_mmap (Ollama)": "use_mmap(Ollama)", + "Use Initials": "使用姓名縮寫", + "use_mlock (Ollama)": "使用 mlock (Ollama)", + "use_mmap (Ollama)": "使用 mmap (Ollama)", "user": "使用者", - "User location successfully retrieved.": "", + "User location successfully retrieved.": "成功取得使用者位置。", "User Permissions": "使用者權限", "Users": "使用者", "Utilize": "使用", - "Valid time units:": "有效時間單位:", - "Valves": "", - "Valves updated": "", - "Valves updated successfully": "", + "Valid time units:": "有效的時間單位:", + "Valves": "閥門", + "Valves updated": "閥門已更新", + "Valves updated successfully": "閥門更新成功", "variable": "變數", - "variable to have them replaced with clipboard content.": "變數將替換為剪貼簿內容", + "variable to have them replaced with clipboard content.": "變數,以便將其替換為剪貼簿內容。", "Version": "版本", - "Voice": "", + "Voice": "語音", "Warning": "警告", - "Warning:": "", - "Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果更新或更改您的嵌入模型,則需要重新匯入所有文件", + "Warning:": "警告:", + "Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果您更新或更改嵌入模型,您將需要重新匯入所有文件。", "Web": "網頁", "Web API": "網頁 API", "Web Loader Settings": "網頁載入器設定", @@ -691,24 +692,24 @@ "Web Search Engine": "網頁搜尋引擎", "Webhook URL": "Webhook URL", "WebUI Settings": "WebUI 設定", - "WebUI will make requests to": "WebUI 將會存取", - "What’s New in": "全新內容", - "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "當歷史被關閉時,這個瀏覽器上的新聊天將不會出現在任何裝置的歷史記錄中", + "WebUI will make requests to": "WebUI 將會對以下網址發出請求", + "What’s New in": "新功能", + "When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "當歷史記錄關閉時,此瀏覽器上的新對話將不會出現在您任何裝置的歷史記錄中。", "Whisper (Local)": "Whisper(本地)", "Widescreen Mode": "寬螢幕模式", "Workspace": "工作區", - "Write a prompt suggestion (e.g. Who are you?)": "寫一個提示詞建議(例如:您是誰?)", - "Write a summary in 50 words that summarizes [topic or keyword].": "寫一個 50 字的摘要來概括 [主題或關鍵詞]。", + "Write a prompt suggestion (e.g. Who are you?)": "撰寫提示詞建議(例如:你是誰?)", + "Write a summary in 50 words that summarizes [topic or keyword].": "用 50 字寫一篇總結 [主題或關鍵字] 的摘要。", "Yesterday": "昨天", "You": "您", - "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "您可以透過下方的「管理」按鈕新增記憶,個人化您的 LLM 互動,使其更有幫助並更符合您的需求。", - "You cannot clone a base model": "您不能複製基礎模型", - "You have no archived conversations.": "您沒有任何已封存的對話", - "You have shared this chat": "您已分享此聊天", - "You're a helpful assistant.": "您是一位善於協助他人的助手。", - "You're now logged in.": "已登入。", - "Your account status is currently pending activation.": "您的帳號狀態目前待啟用。", - "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.": "", - "Youtube": "Youtube", - "Youtube Loader Settings": "Youtube 載入器設定" + "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "您可以透過下方的「管理」按鈕新增記憶,將您與大型語言模型的互動個人化,讓它們更有幫助並更符合您的需求。", + "You cannot clone a base model": "您無法複製基礎模型", + "You have no archived conversations.": "您沒有已封存的對話。", + "You have shared this chat": "您已分享此對話", + "You're a helpful assistant.": "您是一位樂於助人的助手。", + "You're now logged in.": "您已登入。", + "Your account status is currently pending activation.": "您的帳號目前正在等待啟用。", + "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.": "您的所有貢獻將會直接交給外掛開發者;Open WebUI 不會收取任何百分比。然而,所選擇的贊助平臺可能有其自身的費用。", + "Youtube": "YouTube", + "Youtube Loader Settings": "YouTube 載入器設定" } From bf6c6627e8d2ae7e8c0e4da63a6df7c54b60eeb3 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Mon, 5 Aug 2024 01:03:22 +0200 Subject: [PATCH 46/81] revert --- .../chat/Messages/ResponseMessage.svelte | 1205 ++++++++--------- .../chat/Messages/TokenRenderer.svelte | 75 + src/lib/components/common/Image.svelte | 6 +- 3 files changed, 649 insertions(+), 637 deletions(-) create mode 100644 src/lib/components/chat/Messages/TokenRenderer.svelte diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 8cd377bdf2..5cb0cbeef7 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -38,6 +38,7 @@ import Spinner from '$lib/components/common/Spinner.svelte'; import WebSearchResults from './ResponseMessage/WebSearchResults.svelte'; import Sparkles from '$lib/components/icons/Sparkles.svelte'; + import TokenRenderer from './TokenRenderer.svelte'; export let message; export let siblings; @@ -80,25 +81,6 @@ replaceTokens(sanitizeResponseContent(message?.content), model?.name, $user?.name) ); - const renderer = new marked.Renderer(); - - // For code blocks with simple backticks - renderer.codespan = (code) => { - return `${code.replaceAll('&', '&')}`; - }; - - // Open all links in a new tab/window (from https://github.com/markedjs/marked/issues/655#issuecomment-383226346) - const origLinkRenderer = renderer.link; - renderer.link = (href, title, text) => { - const html = origLinkRenderer.call(renderer, href, title, text); - return html.replace(/^
- {#if (message?.files ?? []).filter((f) => f.type === 'image').length > 0} -
- {#each message.files as file} -
- {#if file.type === 'image'} - - {/if} -
- {/each} -
- {/if} +
+ {#if (message?.files ?? []).filter((f) => f.type === 'image').length > 0} +
+ {#each message.files as file} +
+ {#if file.type === 'image'} + + {/if} +
+ {/each} +
+ {/if} -
-
- {#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0} - {@const status = ( - message?.statusHistory ?? [...(message?.status ? [message?.status] : [])] - ).at(-1)} -
- {#if status.done === false} -
- -
- {/if} +
+
+ {#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0} + {@const status = ( + message?.statusHistory ?? [...(message?.status ? [message?.status] : [])] + ).at(-1)} +
+ {#if status.done === false} +
+ +
+ {/if} - {#if status?.action === 'web_search' && status?.urls} - + {#if status?.action === 'web_search' && status?.urls} + +
+
+ {status?.description} +
+
+
+ {:else}
-
+
{status?.description}
- - {:else} -
-
- {status?.description} -
-
- {/if} -
- {/if} - - {#if edit === true} -
-