From 3ef7367f0105c8601f8f8bc4a25084ea25b17e9f Mon Sep 17 00:00:00 2001 From: Aleix Dorca Date: Fri, 21 Nov 2025 09:28:38 +0100 Subject: [PATCH 01/80] Update Catalan translation.json (#19338) --- src/lib/i18n/locales/ca-ES/translation.json | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index 43811816f0..a9ace36dbd 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -66,7 +66,7 @@ "Additional Config": "Configuració addicional", "Additional configuration options for marker. This should be a JSON string with key-value pairs. For example, '{\"key\": \"value\"}'. Supported keys include: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level": "Opcions de configuració addicionals per al marcador. Hauria de ser una cadena JSON amb parelles clau-valor. Per exemple, '{\"key\": \"value\"}'. Les claus compatibles inclouen: disable_links, keep_pageheader_in_output, keep_pagefooter_in_output, filter_blank_pages, drop_repeated_text, layout_coverage_threshold, merge_threshold, height_tolerance, gap_threshold, image_threshold, min_line_length, level_count, default_level", "Additional Parameters": "Paràmetres addicionals", - "Adds filenames, titles, sections, and snippets into the BM25 text to improve lexical recall.": "", + "Adds filenames, titles, sections, and snippets into the BM25 text to improve lexical recall.": "Afegeix noms de fitxer, títols, seccions i fragments al text BM25 per millorar la recuperació lèxica.", "Adjusting these settings will apply changes universally to all users.": "Si ajustes aquesta preferència, els canvis s'aplicaran de manera universal a tots els usuaris.", "admin": "administrador", "Admin": "Administrador", @@ -131,7 +131,7 @@ "API Key created.": "clau API creada.", "API Key Endpoint Restrictions": "Restriccions del punt d'accés de la Clau API", "API keys": "Claus de l'API", - "API Keys": "", + "API Keys": "Claus de l'API", "API Mode": "Mode API", "API Version": "Versió de l'API", "API Version is required": "Versió API requerida", @@ -379,7 +379,7 @@ "Datalab Marker API": "API de Datalab Marker", "DD/MM/YYYY": "DD/MM/YYYY", "December": "Desembre", - "Decrease UI Scale": "", + "Decrease UI Scale": "Disminuir la mida de la interfície gràfica", "Deepgram": "Deepgram", "Default": "Per defecte", "Default (Open AI)": "Per defecte (Open AI)", @@ -458,7 +458,7 @@ "Do not install functions from sources you do not fully trust.": "No instal·lis funcions de fonts en què no confiïs plenament.", "Do not install tools from sources you do not fully trust.": "No instal·lis eines de fonts en què no confiïs plenament.", "Docling": "Docling", - "Docling Parameters": "", + "Docling Parameters": "Paràmetres de Docling", "Docling Server URL required.": "La URL del servidor Docling és necessària", "Document": "Document", "Document Intelligence": "Document Intelligence", @@ -520,7 +520,7 @@ "Embedding Model": "Model d'incrustació", "Embedding Model Engine": "Motor de model d'incrustació", "Embedding model set to \"{{embedding_model}}\"": "Model d'incrustació configurat a \"{{embedding_model}}\"", - "Enable API Keys": "", + "Enable API Keys": "Permetre claus API", "Enable autocomplete generation for chat messages": "Activar la generació automàtica per als missatges del xat", "Enable Code Execution": "Permetre l'execució de codi", "Enable Code Interpreter": "Activar l'intèrpret de codi", @@ -536,14 +536,14 @@ "Endpoint URL": "URL de connexió", "Enforce Temporary Chat": "Forçar els xats temporals", "Enhance": "Millorar", - "Enrich Hybrid Search Text": "", + "Enrich Hybrid Search Text": "Enriquir el text de cerca híbrid", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assegura't que els teus fitxers CSV inclouen 4 columnes en aquest ordre: Nom, Correu electrònic, Contrasenya, Rol.", "Enter {{role}} message here": "Introdueix aquí el missatge de {{role}}", "Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar", "Enter a title for the pending user info overlay. Leave empty for default.": "Introdueix un títol per a la finestra de dades d'usuari pendent. Deixa buit per a valor per defecte.", "Enter a watermark for the response. Leave empty for none.": "Introdueix una marca d'aigua per a la resposta. Deixa-ho buit per a cap.", "Enter additional headers in JSON format": "Introdueix capçaleres addicionals en format JSON", - "Enter additional headers in JSON format (e.g. {\"X-Custom-Header\": \"value\"}": "", + "Enter additional headers in JSON format (e.g. {\"X-Custom-Header\": \"value\"}": "Introduïeix capçaleres addicionals en format JSON (per exemple, {\"X-Custom-Header\": \"value\"}", "Enter additional parameters in JSON format": "Introdueix paràmetres addicionals en format JSON", "Enter api auth string (e.g. username:password)": "Entra la cadena d'autenticació api (p. ex. nom d'usuari:contrasenya)", "Enter Application DN": "Introdueix el DN d'aplicació", @@ -567,7 +567,7 @@ "Enter Docling Server URL": "Introdueix la URL del servidor Docling", "Enter Document Intelligence Endpoint": "Introdueix el punt de connexió de Document Intelligence", "Enter Document Intelligence Key": "Introdueix la clau de Document Intelligence", - "Enter domains separated by commas (e.g., example.com,site.org,!excludedsite.com)": "", + "Enter domains separated by commas (e.g., example.com,site.org,!excludedsite.com)": "Introdueix dominis separats per comes (per exemple, example.com, lloc.org, !excludedsite.com)", "Enter Exa API Key": "Introdueix la clau API de d'EXA", "Enter External Document Loader API Key": "Introdueix la clau API de Document Loader", "Enter External Document Loader URL": "Introdueix la URL de Document Loader", @@ -603,7 +603,7 @@ "Enter Number of Steps (e.g. 50)": "Introdueix el nombre de passos (p. ex. 50)", "Enter Ollama Cloud API Key": "Introdueix la clau API de Ollama Cloud", "Enter Perplexity API Key": "Introdueix la clau API de Perplexity", - "Enter Perplexity Search API URL": "", + "Enter Perplexity Search API URL": "Introduïu l'URL de l'API de cerca de Perplexity", "Enter Playwright Timeout": "Introdueix el timeout de Playwright", "Enter Playwright WebSocket URL": "Introdueix la URL de Playwright WebSocket", "Enter proxy URL (e.g. https://user:password@host:port)": "Entra la URL (p. ex. https://user:password@host:port)", @@ -689,12 +689,12 @@ "Export chat (.json)": "Exportar el xat (.json)", "Export Chats": "Exportar els xats", "Export Config to JSON File": "Exportar la configuració a un arxiu JSON", - "Export Models": "", + "Export Models": "Exportar els models", "Export Presets": "Exportar les configuracions", "Export Prompt Suggestions": "Exportar els suggeriments d'indicació", - "Export Prompts": "", + "Export Prompts": "Exportar les indicacions", "Export to CSV": "Exportar a CSV", - "Export Tools": "", + "Export Tools": "Exportar les eines", "Export Users": "Exportar els usuaris", "External": "Extern", "External Document Loader URL required.": "Fa falta la URL per a Document Loader", @@ -871,20 +871,20 @@ "Import Chats": "Importar xats", "Import Config from JSON File": "Importar la configuració des d'un arxiu JSON", "Import From Link": "Importar des d'un enllaç", - "Import Models": "", + "Import Models": "Importar models", "Import Notes": "Importar nota", "Import Presets": "Importar configuracions", "Import Prompt Suggestions": "Importar suggeriments d'indicacions", - "Import Prompts": "", + "Import Prompts": "Importar indicacions", "Import successful": "Importació correcta", - "Import Tools": "", + "Import Tools": "Importar eines", "Important Update": "Actualització important", "In order to force OCR, performing OCR must be enabled.": "Per forçar l'OCR, cal activar l'OCR.", "Include": "Incloure", "Include `--api-auth` flag when running stable-diffusion-webui": "Inclou `--api-auth` quan executis stable-diffusion-webui", "Include `--api` flag when running stable-diffusion-webui": "Inclou `--api` quan executis stable-diffusion-webui", "Includes SharePoint": "Inclou SharePoint", - "Increase UI Scale": "", + "Increase UI Scale": "Incrementar l'escala de la interfície gràfica", "Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive.": "Influeix amb la rapidesa amb què l'algoritme respon als comentaris del text generat. Una taxa d'aprenentatge més baixa donarà lloc a ajustos més lents, mentre que una taxa d'aprenentatge més alta farà que l'algorisme sigui més sensible.", "Info": "Informació", "Initials": "Inicials", @@ -907,7 +907,7 @@ "Invalid JSON format for ComfyUI Edit Workflow.": "Format JSON no vàlid per al Workflow de ComfyUI d'edició", "Invalid JSON format for ComfyUI Workflow.": "Arxiu JSON de Workflow ComfyUI no vàlid.", "Invalid JSON format for Parameters": "JSON de paràmetres no vàlid", - "Invalid JSON format in {{NAME}}": "", + "Invalid JSON format in {{NAME}}": "Format JSON no vàlid a {{NAME}}", "Invalid JSON format in Additional Config": "Format JSON no vàlid a la configuració addicional", "Invalid JSON format in MinerU Parameters": "Format JSON no vàlid en els paràmetres de MinerU", "Invalid Tag": "Etiqueta no vàlida", @@ -1213,7 +1213,7 @@ "Permissions": "Permisos", "Perplexity API Key": "Clau API de Perplexity", "Perplexity Model": "Model de Perplexity", - "Perplexity Search API URL": "", + "Perplexity Search API URL": "URL API per a Perplexity Search", "Perplexity Search Context Usage": "Utilització del context de cerca de Perplexity", "Personalization": "Personalització", "Picture Description API Config": "Configuració de l'API de la descripció d'imatges", @@ -1653,7 +1653,7 @@ "Type Hugging Face Resolve (Download) URL": "Escriu la URL de Resolució (Descàrrega) de Hugging Face", "Uh-oh! There was an issue with the response.": "Vaja! Hi ha hagut una incidència amb la resposta.", "UI": "UI", - "UI Scale": "", + "UI Scale": "Mida de la interfície gràfica", "Unarchive All": "Desarxivar tot", "Unarchive All Archived Chats": "Desarxivar tots els xats arxivats", "Unarchive Chat": "Desarxivar xat", @@ -1727,8 +1727,8 @@ "Voice": "Veu", "Voice Input": "Entrada de veu", "Voice mode": "Mode de veu", - "Voice Mode Custom Prompt": "", - "Voice Mode Prompt": "", + "Voice Mode Custom Prompt": "Indicació personalitzada per al mode de veu", + "Voice Mode Prompt": "Indicació per al mode de veu", "Warning": "Avís", "Warning:": "Avís:", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Avís: Habilitar això permetrà als usuaris penjar codi arbitrari al servidor.", From a51579a84bec3e2ea50ea741b4566823c288eef1 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 21 Nov 2025 03:49:49 -0500 Subject: [PATCH 02/80] refac/pref: chat import optimization Co-Authored-By: G30 <50341825+silentoplayz@users.noreply.github.com> --- backend/open_webui/models/chats.py | 72 ++++++++++--------- backend/open_webui/routers/chats.py | 21 ++---- src/lib/apis/chats/index.ts | 17 +---- .../chat/Settings/DataControls.svelte | 43 ++++++----- src/lib/components/layout/Sidebar.svelte | 59 +++++++-------- .../layout/Sidebar/RecursiveFolder.svelte | 23 +++--- 6 files changed, 116 insertions(+), 119 deletions(-) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index f1607f0707..f1f7f03642 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -92,6 +92,10 @@ class ChatImportForm(ChatForm): updated_at: Optional[int] = None +class ChatsImportForm(BaseModel): + chats: list[ChatImportForm] + + class ChatTitleMessagesForm(BaseModel): title: str messages: list[dict] @@ -148,42 +152,44 @@ class ChatTable: db.refresh(result) return ChatModel.model_validate(result) if result else None - def import_chat( + def _chat_import_form_to_chat_model( self, user_id: str, form_data: ChatImportForm - ) -> Optional[ChatModel]: - with get_db() as db: - id = str(uuid.uuid4()) - chat = ChatModel( - **{ - "id": id, - "user_id": user_id, - "title": ( - form_data.chat["title"] - if "title" in form_data.chat - else "New Chat" - ), - "chat": form_data.chat, - "meta": form_data.meta, - "pinned": form_data.pinned, - "folder_id": form_data.folder_id, - "created_at": ( - form_data.created_at - if form_data.created_at - else int(time.time()) - ), - "updated_at": ( - form_data.updated_at - if form_data.updated_at - else int(time.time()) - ), - } - ) + ) -> ChatModel: + id = str(uuid.uuid4()) + chat = ChatModel( + **{ + "id": id, + "user_id": user_id, + "title": ( + form_data.chat["title"] if "title" in form_data.chat else "New Chat" + ), + "chat": form_data.chat, + "meta": form_data.meta, + "pinned": form_data.pinned, + "folder_id": form_data.folder_id, + "created_at": ( + form_data.created_at if form_data.created_at else int(time.time()) + ), + "updated_at": ( + form_data.updated_at if form_data.updated_at else int(time.time()) + ), + } + ) + return chat - result = Chat(**chat.model_dump()) - db.add(result) + def import_chats( + self, user_id: str, chats: list[ChatImportForm] + ) -> list[ChatModel]: + with get_db() as db: + chats = [] + + for form_data in chats: + chat = self._chat_import_form_to_chat_model(user_id, form_data) + chats.append(Chat(**chat.model_dump())) + + db.add_all(chats) db.commit() - db.refresh(result) - return ChatModel.model_validate(result) if result else None + return [ChatModel.model_validate(chat) for chat in chats] def update_chat_by_id(self, id: str, chat: dict) -> Optional[ChatModel]: try: diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 2587c5ff8e..9f2ed85882 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -7,9 +7,11 @@ from open_webui.socket.main import get_event_emitter from open_webui.models.chats import ( ChatForm, ChatImportForm, + ChatBulkImportForm, ChatResponse, Chats, ChatTitleIdResponse, + ChatsImportForm, ) from open_webui.models.tags import TagModel, Tags from open_webui.models.folders import Folders @@ -142,26 +144,15 @@ async def create_new_chat(form_data: ChatForm, user=Depends(get_verified_user)): ############################ -# ImportChat +# ImportChats ############################ @router.post("/import", response_model=Optional[ChatResponse]) -async def import_chat(form_data: ChatImportForm, user=Depends(get_verified_user)): +async def import_chats(form_data: ChatsImportForm, user=Depends(get_verified_user)): try: - chat = Chats.import_chat(user.id, form_data) - if chat: - tags = chat.meta.get("tags", []) - for tag_id in tags: - tag_id = tag_id.replace(" ", "_").lower() - tag_name = " ".join([word.capitalize() for word in tag_id.split("_")]) - if ( - tag_id != "none" - and Tags.get_tag_by_name_and_user_id(tag_name, user.id) is None - ): - Tags.insert_new_tag(tag_name, user.id) - - return ChatResponse(**chat.model_dump()) + chats = Chats.import_chats(user.id, form_data.chats) + return [ChatResponse(**chat.model_dump()) for chat in chats] except Exception as e: log.exception(e) raise HTTPException( diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index c548a71dc2..010c80a56f 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -65,15 +65,7 @@ export const unarchiveAllChats = async (token: string) => { return res; }; -export const importChat = async ( - token: string, - chat: object, - meta: object | null, - pinned?: boolean, - folderId?: string | null, - createdAt: number | null = null, - updatedAt: number | null = null -) => { +export const importChats = async (token: string, chats: object[]) => { let error = null; const res = await fetch(`${WEBUI_API_BASE_URL}/chats/import`, { @@ -84,12 +76,7 @@ export const importChat = async ( authorization: `Bearer ${token}` }, body: JSON.stringify({ - chat: chat, - meta: meta ?? {}, - pinned: pinned, - folder_id: folderId, - created_at: createdAt ?? null, - updated_at: updatedAt ?? null + chats }) }) .then(async (res) => { diff --git a/src/lib/components/chat/Settings/DataControls.svelte b/src/lib/components/chat/Settings/DataControls.svelte index 3aef3b1c62..f4e8bb76f2 100644 --- a/src/lib/components/chat/Settings/DataControls.svelte +++ b/src/lib/components/chat/Settings/DataControls.svelte @@ -16,8 +16,8 @@ deleteAllChats, getAllChats, getChatList, - importChat, - getPinnedChatList + getPinnedChatList, + importChats } from '$lib/apis/chats'; import { getImportOrigin, convertOpenAIChats } from '$lib/utils'; import { onMount, getContext } from 'svelte'; @@ -52,7 +52,7 @@ console.log('Unable to import chats:', error); } } - importChats(chats); + importChatsHandler(chats); }; if (importFiles.length > 0) { @@ -60,24 +60,33 @@ } } - const importChats = async (_chats) => { - for (const chat of _chats) { - console.log(chat); - + const importChatsHandler = async (_chats) => { + const chats = _chats.map((chat) => { if (chat.chat) { - await importChat( - localStorage.token, - chat.chat, - chat.meta ?? {}, - false, - null, - chat?.created_at ?? null, - chat?.updated_at ?? null - ); + return { + chat: chat.chat, + meta: chat.meta ?? {}, + pinned: false, + folder_id: chat?.folder_id ?? null, + created_at: chat?.created_at ?? null, + updated_at: chat?.updated_at ?? null + }; } else { // Legacy format - await importChat(localStorage.token, chat, {}, false, null); + return { + chat: chat, + meta: {}, + pinned: false, + folder_id: null, + created_at: chat?.created_at ?? null, + updated_at: chat?.updated_at ?? null + }; } + }); + + const res = await importChats(localStorage.token, chats); + if (res) { + toast.success(`Successfully imported ${res.length} chats.`); } currentChatPage.set(1); diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index 1ccb2c718b..915ec41cf5 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -38,7 +38,7 @@ toggleChatPinnedStatusById, getChatById, updateChatFolderIdById, - importChat + importChats } from '$lib/apis/chats'; import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; @@ -227,15 +227,16 @@ for (const item of items) { console.log(item); if (item.chat) { - await importChat( - localStorage.token, - item.chat, - item?.meta ?? {}, - pinned, - folderId, - item?.created_at ?? null, - item?.updated_at ?? null - ); + await importChats(localStorage.token, [ + { + chat: item.chat, + meta: item?.meta ?? {}, + pinned: pinned, + folder_id: folderId, + created_at: item?.created_at ?? null, + updated_at: item?.updated_at ?? null + } + ]); } } @@ -999,15 +1000,16 @@ return null; }); if (!chat && item) { - chat = await importChat( - localStorage.token, - item.chat, - item?.meta ?? {}, - false, - null, - item?.created_at ?? null, - item?.updated_at ?? null - ); + chat = await importChats(localStorage.token, [ + { + chat: item.chat, + meta: item?.meta ?? {}, + pinned: false, + folder_id: null, + created_at: item?.created_at ?? null, + updated_at: item?.updated_at ?? null + } + ]); } if (chat) { @@ -1064,15 +1066,16 @@ return null; }); if (!chat && item) { - chat = await importChat( - localStorage.token, - item.chat, - item?.meta ?? {}, - false, - null, - item?.created_at ?? null, - item?.updated_at ?? null - ); + chat = await importChats(localStorage.token, [ + { + chat: item.chat, + meta: item?.meta ?? {}, + pinned: false, + folder_id: null, + created_at: item?.created_at ?? null, + updated_at: item?.updated_at ?? null + } + ]); } if (chat) { diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte index be8343c90d..331719647d 100644 --- a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte +++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte @@ -24,8 +24,8 @@ getChatById, getChatsByFolderId, getChatListByFolderId, - importChat, - updateChatFolderIdById + updateChatFolderIdById, + importChats } from '$lib/apis/chats'; import ChevronDown from '../../icons/ChevronDown.svelte'; @@ -152,15 +152,16 @@ return null; }); if (!chat && item) { - chat = await importChat( - localStorage.token, - item.chat, - item?.meta ?? {}, - false, - null, - item?.created_at ?? null, - item?.updated_at ?? null - ).catch((error) => { + chat = await importChats(localStorage.token, [ + { + chat: item.chat, + meta: item?.meta ?? {}, + pinned: false, + folder_id: null, + created_at: item?.created_at ?? null, + updated_at: item?.updated_at ?? null + } + ]).catch((error) => { toast.error(`${error}`); return null; }); From 0dd9ad7ffc45afd9f5f510706f1e16e353bfca56 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 21 Nov 2025 03:58:30 -0500 Subject: [PATCH 03/80] refac --- backend/open_webui/routers/chats.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 9f2ed85882..9659082db9 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -7,11 +7,10 @@ from open_webui.socket.main import get_event_emitter from open_webui.models.chats import ( ChatForm, ChatImportForm, - ChatBulkImportForm, + ChatsImportForm, ChatResponse, Chats, ChatTitleIdResponse, - ChatsImportForm, ) from open_webui.models.tags import TagModel, Tags from open_webui.models.folders import Folders From 0c18cd67d54af51a513144c9f48f0503c8182744 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 21 Nov 2025 04:13:59 -0500 Subject: [PATCH 04/80] refac/fix: openai edit multiple images --- backend/open_webui/routers/images.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index c4e67ae9ea..4cc2a99101 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -838,13 +838,13 @@ async def image_edits( except Exception as e: raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) - def get_image_file_item(base64_string): + def get_image_file_item(base64_string, param_name="image"): data = base64_string header, encoded = data.split(",", 1) mime_type = header.split(";")[0].lstrip("data:") image_data = base64.b64decode(encoded) return ( - "image", + param_name, ( f"{uuid.uuid4()}.png", io.BytesIO(image_data), @@ -879,7 +879,7 @@ async def image_edits( files = [get_image_file_item(form_data.image)] elif isinstance(form_data.image, list): for img in form_data.image: - files.append(get_image_file_item(img)) + files.append(get_image_file_item(img, "image[]")) url_search_params = "" if request.app.state.config.IMAGES_EDIT_OPENAI_API_VERSION: From 2b58191b8270805f8606c4826fae7cb699e9e713 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 21 Nov 2025 04:32:49 -0500 Subject: [PATCH 05/80] refac --- backend/open_webui/utils/middleware.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 5095bb418b..eebe41d571 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1130,7 +1130,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): pass event_emitter = get_event_emitter(metadata) - event_call = get_event_call(metadata) + event_caller = get_event_call(metadata) oauth_token = None try: @@ -1144,14 +1144,13 @@ async def process_chat_payload(request, form_data, user, metadata, model): extra_params = { "__event_emitter__": event_emitter, - "__event_call__": event_call, + "__event_call__": event_caller, "__user__": user.model_dump() if isinstance(user, UserModel) else {}, "__metadata__": metadata, + "__oauth_token__": oauth_token, "__request__": request, "__model__": model, - "__oauth_token__": oauth_token, } - # Initialize events to store additional event to be sent to the client # Initialize contexts and citation if getattr(request.state, "direct", False) and hasattr(request.state, "model"): From b88f829dbb76842604b0c898be8023b0f40daeab Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 21 Nov 2025 05:28:55 -0500 Subject: [PATCH 06/80] enh: clone system models Co-Authored-By: G30 <50341825+silentoplayz@users.noreply.github.com> --- src/lib/components/admin/Settings/Models.svelte | 14 ++++++++++++++ .../admin/Settings/Models/ModelMenu.svelte | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index 500788c4e8..1c1fc6512b 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -38,6 +38,7 @@ import EyeSlash from '$lib/components/icons/EyeSlash.svelte'; import Eye from '$lib/components/icons/Eye.svelte'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; + import { goto } from '$app/navigation'; let shiftKey = false; @@ -200,6 +201,16 @@ } }; + const cloneHandler = async (model) => { + sessionStorage.model = JSON.stringify({ + ...model, + base_model_id: model.id, + id: `${model.id}-clone`, + name: `${model.name} (Clone)` + }); + goto('/workspace/models/create'); + }; + const exportModelHandler = async (model) => { let blob = new Blob([JSON.stringify([model])], { type: 'application/json' @@ -419,6 +430,9 @@ copyLinkHandler={() => { copyLinkHandler(model); }} + cloneHandler={() => { + cloneHandler(model); + }} onClose={() => {}} > {/if} - {#if models.length && ($user?.role === 'admin' || $user?.permissions?.workspace?.models_export)} + {#if total && ($user?.role === 'admin' || $user?.permissions?.workspace?.models_export)} {/if} diff --git a/src/lib/components/chat/Messages/Markdown/SourceToken.svelte b/src/lib/components/chat/Messages/Markdown/SourceToken.svelte new file mode 100644 index 0000000000..9d0bbcd6ba --- /dev/null +++ b/src/lib/components/chat/Messages/Markdown/SourceToken.svelte @@ -0,0 +1,70 @@ + + +{#if (token?.ids ?? []).length == 1} + +{:else} + + + + + +
+ {#each token.ids as sourceId} +
+ +
+ {/each} +
+
+
+{/if} diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 98a9dafcc7..2238b21abb 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -797,11 +797,11 @@ onTaskClick={async (e) => { console.log(e); }} - onSourceClick={async (id, idx) => { - console.log(id, idx); + onSourceClick={async (id) => { + console.log(id); if (citationsElement) { - citationsElement?.showSourceModal(idx - 1); + citationsElement?.showSourceModal(id); } }} onAddMessages={({ modelId, parentId, messages }) => { diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index f9fb4236f2..5b92da8b8e 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -32,7 +32,7 @@ function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -export const replaceTokens = (content, sourceIds, char, user) => { +export const replaceTokens = (content, char, user) => { const tokens = [ { regex: /{{char}}/gi, replacement: char }, { regex: /{{user}}/gi, replacement: user }, @@ -67,30 +67,6 @@ export const replaceTokens = (content, sourceIds, char, user) => { } }); - if (Array.isArray(sourceIds)) { - // Match both [1], [2], and [1,2,3] forms - const multiRefRegex = /\[([\d,\s]+)\]/g; - segment = segment.replace(multiRefRegex, (match, group) => { - // Extract numbers like 1,2,3 - const indices = group - .split(',') - .map((n) => parseInt(n.trim(), 10)) - .filter((n) => !isNaN(n)); - - // Replace each index with a tag - const sources = indices - .map((idx) => { - const sourceId = sourceIds[idx - 1]; - return sourceId - ? `` - : `[${idx}]`; - }) - .join(''); - - return sources; - }); - } - return segment; }); diff --git a/src/lib/utils/marked/citation-extension.ts b/src/lib/utils/marked/citation-extension.ts new file mode 100644 index 0000000000..dad266c9f0 --- /dev/null +++ b/src/lib/utils/marked/citation-extension.ts @@ -0,0 +1,55 @@ +export function citationExtension() { + return { + name: 'citation', + level: 'inline' as const, + + start(src: string) { + // Trigger on any [number] + return src.search(/\[(\d[\d,\s]*)\]/); + }, + + tokenizer(src: string) { + // Avoid matching footnotes + if (/^\[\^/.test(src)) return; + + // Match ONE OR MORE adjacent [1] or [1,2] blocks + // Example matched: "[1][2,3][4]" + const rule = /^(\[(?:\d[\d,\s]*)\])+/; + const match = rule.exec(src); + if (!match) return; + + const raw = match[0]; + + // Extract ALL bracket groups inside the big match + const groupRegex = /\[([\d,\s]+)\]/g; + const ids: number[] = []; + let m: RegExpExecArray | null; + + while ((m = groupRegex.exec(raw))) { + const parsed = m[1] + .split(',') + .map((n) => parseInt(n.trim(), 10)) + .filter((n) => !isNaN(n)); + + ids.push(...parsed); + } + + return { + type: 'citation', + raw, + ids // merged list + }; + }, + + renderer(token: any) { + // e.g. "1,2,3" + return token.ids.join(','); + } + }; +} + +export default function () { + return { + extensions: [citationExtension()] + }; +} diff --git a/src/lib/utils/marked/footnote-extension.ts b/src/lib/utils/marked/footnote-extension.ts new file mode 100644 index 0000000000..4ef95be6a8 --- /dev/null +++ b/src/lib/utils/marked/footnote-extension.ts @@ -0,0 +1,38 @@ +// footnote-extension.ts +// Simple extension for marked to support footnote references like [^1], [^note] + +function escapeHtml(s: string) { + return s.replace( + /[&<>"']/g, + (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]! + ); +} + +export function footnoteExtension() { + return { + name: 'footnote', + level: 'inline' as const, + start(src: string) { + return src.search(/\[\^\s*[a-zA-Z0-9_-]+\s*\]/); + }, + tokenizer(src: string) { + const rule = /^\[\^\s*([a-zA-Z0-9_-]+)\s*\]/; + const match = rule.exec(src); + if (match) { + const escapedText = escapeHtml(match[1]); + return { + type: 'footnote', + raw: match[0], + text: match[1], + escapedText: escapedText + }; + } + } + }; +} + +export default function () { + return { + extensions: [footnoteExtension()] + }; +} From 9b7c3ff999c3e45106bb4240c5123c6fa3c53bc8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 23 Nov 2025 18:31:59 -0500 Subject: [PATCH 36/80] refac --- .../components/chat/Messages/Markdown/SourceToken.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/components/chat/Messages/Markdown/SourceToken.svelte b/src/lib/components/chat/Messages/Markdown/SourceToken.svelte index 9d0bbcd6ba..fdc422c62a 100644 --- a/src/lib/components/chat/Messages/Markdown/SourceToken.svelte +++ b/src/lib/components/chat/Messages/Markdown/SourceToken.svelte @@ -8,6 +8,7 @@ export let onClick: Function = () => {}; let containerElement; + let openPreview = false; // Helper function to return only the domain from a URL function getDomain(url: string): string { @@ -40,10 +41,13 @@ {#if (token?.ids ?? []).length == 1} {:else} - + -
+
{folder.name}
diff --git a/src/lib/components/common/EmojiPicker.svelte b/src/lib/components/common/EmojiPicker.svelte index cc761039b8..0be2e59c0c 100644 --- a/src/lib/components/common/EmojiPicker.svelte +++ b/src/lib/components/common/EmojiPicker.svelte @@ -117,13 +117,13 @@ -
+
Date: Sun, 23 Nov 2025 20:37:29 -0500 Subject: [PATCH 50/80] refac: styling --- src/lib/components/common/EmojiPicker.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/common/EmojiPicker.svelte b/src/lib/components/common/EmojiPicker.svelte index 0be2e59c0c..624bb7be1b 100644 --- a/src/lib/components/common/EmojiPicker.svelte +++ b/src/lib/components/common/EmojiPicker.svelte @@ -117,7 +117,7 @@ Date: Sun, 23 Nov 2025 21:08:13 -0500 Subject: [PATCH 51/80] refac: prompt suggestions component Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com> --- .../admin/Settings/Interface.svelte | 199 +--------------- .../workspace/Models/ModelEditor.svelte | 61 +---- .../workspace/Models/PromptSuggestions.svelte | 218 ++++++++++++++++++ 3 files changed, 228 insertions(+), 250 deletions(-) create mode 100644 src/lib/components/workspace/Models/PromptSuggestions.svelte diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte index 58165a8e5f..080cec09ba 100644 --- a/src/lib/components/admin/Settings/Interface.svelte +++ b/src/lib/components/admin/Settings/Interface.svelte @@ -21,6 +21,7 @@ import Textarea from '$lib/components/common/Textarea.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; import Banners from './Interface/Banners.svelte'; + import PromptSuggestions from '$lib/components/workspace/Models/PromptSuggestions.svelte'; const dispatch = createEventDispatcher(); @@ -466,201 +467,13 @@
{#if $user?.role === 'admin'} -
-
-
- {$i18n.t('Default Prompt Suggestions')} -
+ - + {#if promptSuggestions.length > 0} +
+ {$i18n.t('Adjusting these settings will apply changes universally to all users.')}
-
- {#each promptSuggestions as prompt, promptIdx} -
-
-
- - - -
- -
- -