From 15bd5040f19d7bea246a91cb2bec8f78374b0de1 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Fri, 26 Sep 2025 11:56:34 +0800 Subject: [PATCH 01/84] fix: fixed the hover effect for the MessageInput Integrations button --- src/lib/components/chat/MessageInput.svelte | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index 18160e8cb95..c4b2d4520db 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -1500,11 +1500,11 @@ ); }} type="button" - class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden hover:bg-gray-50 dark:hover:bg-gray-800 {selectedFilterIds.includes( + class="group p-[7px] flex gap-1.5 items-center text-sm rounded-full transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {selectedFilterIds.includes( filterId ) - ? 'text-sky-500 dark:text-sky-300 bg-sky-50 dark:bg-sky-400/10 border border-sky-200/40 dark:border-sky-500/20' - : 'bg-transparent text-gray-600 dark:text-gray-300 '} capitalize" + ? 'text-sky-500 dark:text-sky-300 bg-sky-50 hover:bg-sky-100 dark:bg-sky-400/10 dark:hover:bg-sky-600/10 border border-sky-200/40 dark:border-sky-500/20' + : 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 '} capitalize" > {#if filter?.icon}
@@ -1533,10 +1533,10 @@
From 36da2b06a53db377d943390d21ee609bcb8dd50f Mon Sep 17 00:00:00 2001 From: joaoback <156559121+joaoback@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:18:27 -0300 Subject: [PATCH 06/84] Update translation.json (pt-BR) translation of the new items that were included in the latest version. --- src/lib/i18n/locales/pt-BR/translation.json | 46 ++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 3ea8aced0f2..2bed7dcea0a 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -149,7 +149,7 @@ "Attach file from knowledge": "Anexar arquivo da base de conhecimento", "Attach Knowledge": "Anexar Base de Conhecimento", "Attach Notes": "Anexar Notas", - "Attach Webpage": "", + "Attach Webpage": "Anexar Página Web", "Attention to detail": "Atenção aos detalhes", "Attribute for Mail": "Atributo para E-mail", "Attribute for Username": "Atribuir para nome de usuário", @@ -774,7 +774,7 @@ "Generate an image": "Gerar uma imagem", "Generate Image": "Gerar Imagem", "Generate prompt pair": "Gerar par de prompts", - "Generated Image": "", + "Generated Image": "Imagem gerada", "Generating search query": "Gerando consulta de pesquisa", "Generating...": "Gerando...", "Get information on {{name}} in the UI": "Obtenha informações sobre {{name}} na IU", @@ -934,7 +934,7 @@ "Llama.cpp": "", "LLMs can make mistakes. Verify important information.": "LLMs podem cometer erros. Verifique informações importantes.", "Loader": "Carregador", - "Loading Kokoro.js...": "", + "Loading Kokoro.js...": "Carregando Kokoro.js...", "Loading...": "Carregando...", "Local": "", "Local Task Model": "Modelo de Tarefa Local", @@ -965,7 +965,7 @@ "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Máximo de 3 modelos podem ser baixados simultaneamente. Por favor, tente novamente mais tarde.", "May": "Maio", "MCP": "", - "MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "", + "MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "O suporte ao MCP é experimental e suas especificações mudam com frequência, o que pode levar a incompatibilidades. O suporte à especificação OpenAPI é mantido diretamente pela equipe do Open WebUI, tornando-o a opção mais confiável para compatibilidade.", "Medium": "Médio", "Memories accessible by LLMs will be shown here.": "Memórias acessíveis por LLMs serão mostradas aqui.", "Memory": "Memória", @@ -1068,7 +1068,7 @@ "None": "Nenhum", "Not factually correct": "Não está factualmente correto", "Not helpful": "Não é útil", - "Not Registered": "", + "Not Registered": "Não registrado", "Note": "Nota", "Note deleted successfully": "Nota excluída com sucesso", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Se você definir uma pontuação mínima, a pesquisa retornará apenas documentos com pontuação igual ou superior à pontuação mínima.", @@ -1178,13 +1178,13 @@ "Please do not close the settings page while loading the model.": "Não feche a página de configurações enquanto estiver carregando o modelo.", "Please enter a message or attach a file.": "Por favor, insira uma mensagem ou anexe um arquivo.", "Please enter a prompt": "Por favor, digite um prompt", - "Please enter a valid ID": "", + "Please enter a valid ID": "Por favor, insira um ID válido", "Please enter a valid path": "Por favor, insira um caminho válido", "Please enter a valid URL": "Por favor, insira uma URL válido", - "Please enter a valid URL.": "", + "Please enter a valid URL.": "Por favor, insira uma URL válida", "Please fill in all fields.": "Por favor, preencha todos os campos.", - "Please register the OAuth client": "", - "Please save the connection to persist the OAuth client information and do not change the ID": "", + "Please register the OAuth client": "Por favor, registre o cliente OAuth", + "Please save the connection to persist the OAuth client information and do not change the ID": "Salve a conexão para persistir as informações do cliente OAuth e não altere o ID", "Please select a model first.": "Selecione um modelo primeiro.", "Please select a model.": "Selecione um modelo.", "Please select a reason": "Por favor, seleccione uma razão", @@ -1224,7 +1224,7 @@ "Re-rank models by topic similarity": "Reclassificação de modelos por similaridade de tópico", "Read": "Ler", "Read Aloud": "Ler em Voz Alta", - "Read more →": "", + "Read more →": "Leia mais →", "Reason": "Razão", "Reasoning Effort": "Esforço de raciocínio", "Reasoning Tags": "Tags de raciocínio", @@ -1237,11 +1237,11 @@ "Refused when it shouldn't have": "Recusado quando não deveria", "Regenerate": "Gerar novamente", "Regenerate Menu": "Regenerar Menu", - "Register Again": "", - "Register Client": "", - "Registered": "", - "Registration failed": "", - "Registration successful": "", + "Register Again": "Registre-se novamente", + "Register Client": "Registrar cliente", + "Registered": "Registrado", + "Registration failed": "O registro falhou", + "Registration successful": "Registro realizado com sucesso", "Reindex": "Reindexar", "Reindex Knowledge Base Vectors": "Reindexar vetores da base de conhecimento", "Release Notes": "Notas de Lançamento", @@ -1259,7 +1259,7 @@ "Rename": "Renomear", "Reorder Models": "Reordenar modelos", "Reply in Thread": "Responder no tópico", - "Reply to thread...": "", + "Reply to thread...": "Responder ao tópico...", "required": "obrigatório", "Reranking Engine": "Motor de Reclassificação", "Reranking Model": "Modelo de Reclassificação", @@ -1467,7 +1467,7 @@ "System": "Sistema", "System Instructions": "Instruções do sistema", "System Prompt": "Prompt do Sistema", - "Table Mode": "", + "Table Mode": "Modo de Tabela", "Tags": "", "Tags Generation": "Geração de tags", "Tags Generation Prompt": "Prompt para geração de Tags", @@ -1583,7 +1583,7 @@ "TTS Settings": "Configurações TTS", "TTS Voice": "Voz TTS", "Type": "Tipo", - "Type here...": "", + "Type here...": "Digite aqui...", "Type Hugging Face Resolve (Download) URL": "Digite o URL de download do Hugging Face", "Uh-oh! There was an issue with the response.": "Opa! Houve um problema com a resposta.", "UI": "Interface", @@ -1633,8 +1633,8 @@ "User Webhooks": "Webhooks do usuário", "Username": "Nome do Usuário", "Users": "Usuários", - "Uses DefaultAzureCredential to authenticate": "", - "Uses OAuth 2.1 Dynamic Client Registration": "", + "Uses DefaultAzureCredential to authenticate": "Usa DefaultAzureCredential para autenticar", + "Uses OAuth 2.1 Dynamic Client Registration": "Utiliza o registro dinâmico de cliente OAuth 2.1", "Using Entire Document": "Usando o documento inteiro", "Using Focused Retrieval": "Usando Recuperação Focada", "Using the default arena model with all models. Click the plus button to add custom models.": "Usando a arena de modelos padrão para todos os modelos. Clique no botão mais para adicionar modelos personalizados.", @@ -1663,7 +1663,7 @@ "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: a execução do Jupyter permite a execução de código arbitrário, o que representa sérios riscos de segurança. Prossiga com extremo cuidado.", "Web": "Web", "Web API": "API Web", - "Web Loader Engine": "", + "Web Loader Engine": "Motor de carregamento da Web", "Web Search": "Pesquisa na Web", "Web Search Engine": "Mecanismo de Busca na Web", "Web Search in Chat": "Pesquisa na Web no Chat", @@ -1704,8 +1704,8 @@ "You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Você só pode conversar com no máximo {{maxCount}} arquivo(s) de cada vez.", "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Você pode personalizar suas interações com LLMs adicionando memórias através do botão 'Gerenciar' abaixo, tornando-as mais úteis e adaptadas a você.", "You cannot upload an empty file.": "Você não pode carregar um arquivo vazio.", - "You do not have permission to send messages in this channel.": "", - "You do not have permission to send messages in this thread.": "", + "You do not have permission to send messages in this channel.": "Você não tem permissão para enviar mensagens neste canal.", + "You do not have permission to send messages in this thread.": "Você não tem permissão para enviar mensagens neste tópico.", "You do not have permission to upload files.": "Você não tem permissão para fazer upload de arquivos.", "You have no archived conversations.": "Você não tem conversas arquivadas.", "You have shared this chat": "Você compartilhou este chat", From ac6292b812f400b9180c5a26b5ccae7611a88703 Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Fri, 26 Sep 2025 13:28:35 -0400 Subject: [PATCH 07/84] Fix: truncate long model tags with a character limit Long model tags on the Models page in the workspace section were not truncated consistently, which could cause layout issues. This change implements a hard character limit of 32 characters on the model tags. Tags longer than 32 characters are truncated with an ellipsis (...) directly in the code. The full tag name remains available in the tooltip. --- src/lib/components/workspace/Models.svelte | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index 9710a71d4e9..f6a2deb40ef 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -305,16 +305,18 @@ {#each tags as tag} - + + + {/each} From b516431569b57d58de54d84b1e9d0269b59d597e Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Fri, 26 Sep 2025 15:03:44 -0400 Subject: [PATCH 08/84] fix: truncate long filter tags in model selector and prevent wrapping This commit addresses an issue where long filter tags at the top of the model selector dropdown were not truncated correctly and would wrap to a new line, causing layout issues. - A hard character limit of 16 characters is applied to the filter tags within the `Selector.svelte` component. Tags longer than 16 characters are truncated with an ellipsis (...) directly in the code. The full tag name remains available in the tooltip. - The `whitespace-nowrap` class has been added to the tag container to ensure that the tags remain on a single, horizontally scrollable line. --- .../chat/ModelSelector/Selector.svelte | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/components/chat/ModelSelector/Selector.svelte b/src/lib/components/chat/ModelSelector/Selector.svelte index 29eb85eb409..ddf4e2d3ca5 100644 --- a/src/lib/components/chat/ModelSelector/Selector.svelte +++ b/src/lib/components/chat/ModelSelector/Selector.svelte @@ -435,7 +435,7 @@ }} >
{#if items.find((item) => item.model?.connection_type === 'local') || items.find((item) => item.model?.connection_type === 'external') || items.find((item) => item.model?.direct) || tags.length > 0} @@ -500,18 +500,20 @@ {/if} {#each tags as tag} - + + + {/each}
From dcb0933149a7a5ac0a653edb7f77593c4ed1ef7d Mon Sep 17 00:00:00 2001 From: SZegotaM Date: Fri, 26 Sep 2025 21:16:35 +0200 Subject: [PATCH 09/84] Update German translations in translation.json Added missing German translation --- src/lib/i18n/locales/de-DE/translation.json | 82 ++++++++++----------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 1c4f46400a1..8447d49f918 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -16,7 +16,7 @@ "{{COUNT}} Replies": "{{COUNT}} Antworten", "{{COUNT}} Sources": "{{COUNT}} Quellen", "{{COUNT}} words": "{{COUNT}} Wörter", - "{{LOCALIZED_DATE}} at {{LOCALIZED_TIME}}": "", + "{{LOCALIZED_DATE}} at {{LOCALIZED_TIME}}": "{{LOCALIZED_DATE}} um {{LOCALIZED_TIME}}", "{{model}} download has been canceled": "Der Download von {{model}} wurde abgebrochen", "{{user}}'s Chats": "{{user}}s Chats", "{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich", @@ -147,9 +147,9 @@ "Ask a question": "Stellen Sie eine Frage", "Assistant": "Assistent", "Attach file from knowledge": "Datei aus Wissensspeicher anhängen", - "Attach Knowledge": "", - "Attach Notes": "", - "Attach Webpage": "", + "Attach Knowledge": "Wissensspeicher anhängen", + "Attach Notes": "Notizen anhängen", + "Attach Webpage": "Webseite anhängen", "Attention to detail": "Aufmerksamkeit für Details", "Attribute for Mail": "Attribut für E-Mail", "Attribute for Username": "Attribut für Benutzername", @@ -359,7 +359,7 @@ "Custom Parameter Value": "Benutzerdefinierter Parameter Wert", "Danger Zone": "Gefahrenzone", "Dark": "Dunkel", - "Data Controls": "", + "Data Controls": "Datenverwaltung", "Database": "Datenbank", "Datalab Marker API": "Datalab Marker API", "Datalab Marker API Key required.": "Datalab Marker API-Schlüssel erforderlich.", @@ -371,8 +371,8 @@ "Default (SentenceTransformers)": "Standard (SentenceTransformers)", "Default action buttons will be used.": "Es werden Standard-Aktionsschaltflächen verwendet.", "Default description enabled": "Standard Beschreibung aktiviert", - "Default Features": "", - "Default Filters": "", + "Default Features": "Standardfunktionen", + "Default Filters": "Standardfilter", "Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "Der Standardmodus funktioniert mit einer breiteren Auswahl von Modellen, indem er Werkzeuge einmal vor der Ausführung aufruft. Der native Modus nutzt die integrierten Tool-Aufrufmöglichkeiten des Modells, erfordert jedoch, dass das Modell diese Funktion von Natur aus unterstützt.", "Default Model": "Standardmodell", "Default model updated": "Standardmodell aktualisiert", @@ -576,11 +576,11 @@ "Enter name": "Name eingeben", "Enter New Password": "Neues Passwort eingeben", "Enter Number of Steps (e.g. 50)": "Geben Sie die Anzahl an Schritten ein (z. B. 50)", - "Enter Ollama Cloud API Key": "", + "Enter Ollama Cloud API Key": "Geben Sie den Ollama Cloud API-Schlüssel ein", "Enter Perplexity API Key": "Geben Sie den Perplexity API-Schlüssel ein", "Enter Playwright Timeout": "Playwright Timeout eingeben", "Enter Playwright WebSocket URL": "Geben Sie die Playwright WebSocket-URL ein", - "Enter proxy URL (e.g. https://user:password@host:port)": "Geben sie die Proxy-URL ein (z. B. https://user:password@host:port)", + "Enter proxy URL (e.g. https://user:password@host:port)": "Geben Sie die Proxy-URL ein (z. B. https://user:password@host:port)", "Enter reasoning effort": "Geben Sie den Reasoning Effort ein", "Enter Sampler (e.g. Euler a)": "Geben Sie den Sampler ein (z. B. Euler a)", "Enter Scheduler (e.g. Karras)": "Geben Sie den Scheduler ein (z. B. Karras)", @@ -728,13 +728,13 @@ "Firecrawl API Key": "Firecrawl API-Schlüssel", "Floating Quick Actions": "Schnellaktionen", "Focus chat input": "Chat-Eingabe fokussieren", - "Folder Background Image": "", + "Folder Background Image": "Ordner Hintergrundbild", "Folder deleted successfully": "Ordner erfolgreich gelöscht", "Folder Name": "Ordner-Name", "Folder name cannot be empty.": "Ordnername darf nicht leer sein.", "Folder name updated successfully": "Ordnername erfolgreich aktualisiert", "Folder updated successfully": "Ordner erfolgreich aktualisiert", - "Folders": "", + "Folders": "Ordner", "Follow up": "Folgefragen", "Follow Up Generation": "Folgefragen Generierung", "Follow Up Generation Prompt": "Prompt für Folgefragen Generierung", @@ -774,7 +774,7 @@ "Generate an image": "Bild erzeugen", "Generate Image": "Bild erzeugen", "Generate prompt pair": "Prompt-Paar generieren", - "Generated Image": "", + "Generated Image": "Erzeugtes Bild", "Generating search query": "Suchanfrage wird erstellt", "Generating...": "Generiere...", "Get information on {{name}} in the UI": "Informationen zu {{name}} in der Benutzeroberfläche abrufen", @@ -816,7 +816,7 @@ "Hybrid Search": "Hybride Suche", "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.": "Ich bestätige, dass ich gelesen habe und die Auswirkungen meiner Aktion verstehe. Mir sind die Risiken bewusst, die mit der Ausführung beliebigen Codes verbunden sind, und ich habe die Vertrauenswürdigkeit der Quelle überprüft.", "ID": "ID", - "ID cannot contain \":\" or \"|\" characters": "", + "ID cannot contain \":\" or \"|\" characters": "ID darf keine \":\" oder \"|\" Zeichen enthalten", "iframe Sandbox Allow Forms": "iFrame-Sandbox: Formulare erlauben", "iframe Sandbox Allow Same Origin": "iFrame-Sandbox: Gleichen Origin erlauben", "Ignite curiosity": "Neugier entfachen", @@ -866,7 +866,7 @@ "Install from Github URL": "Von GitHub-URL installieren", "Instant Auto-Send After Voice Transcription": "Spracherkennung direkt absenden", "Integration": "Integration", - "Integrations": "", + "Integrations": "Integrationen", "Interface": "Oberfläche", "Invalid file content": "Ungültiger Dateiinhalt", "Invalid file format.": "Ungültiges Dateiformat.", @@ -965,7 +965,7 @@ "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es können maximal 3 Modelle gleichzeitig heruntergeladen werden. Bitte versuchen Sie es später erneut.", "May": "Mai", "MCP": "", - "MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "", + "MCP support is experimental and its specification changes often, which can lead to incompatibilities. OpenAPI specification support is directly maintained by the Open WebUI team, making it the more reliable option for compatibility.": "Die MCP-Unterstützung ist experimentell und ihre Spezifikation ändert sich häufig, was zu Inkompatibilitäten führen kann. Die Unterstützung der OpenAPI-Spezifikation wird direkt vom Open‑WebUI‑Team gepflegt und ist daher die verlässlichere Option in Bezug auf Kompatibilität.", "Medium": "Mittel", "Memories accessible by LLMs will be shown here.": "Erinnerungen, die für Modelle zugänglich sind, werden hier angezeigt.", "Memory": "Erinnerungen", @@ -1054,7 +1054,7 @@ "No models found": "Keine Modelle gefunden", "No models selected": "Keine Modelle ausgewählt", "No Notes": "Keine Notizen", - "No notes found": "", + "No notes found": "Keine Notizen gefunden", "No results": "Keine Ergebnisse gefunden", "No results found": "Keine Ergebnisse gefunden", "No search query generated": "Keine Suchanfrage generiert", @@ -1068,8 +1068,8 @@ "None": "Nichts", "Not factually correct": "Nicht sachlich korrekt", "Not helpful": "Nicht hilfreich", - "Not Registered": "", - "Note": "", + "Not Registered": "Nicht registriert", + "Note": "Notiz", "Note deleted successfully": "Notiz erfolgreich gelöscht", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn Sie eine Mindestpunktzahl festlegen, werden in der Suche nur Dokumente mit einer Punktzahl größer oder gleich der Mindestpunktzahl zurückgegeben.", "Notes": "Notizen", @@ -1087,7 +1087,7 @@ "Ollama": "Ollama", "Ollama API": "Ollama-API", "Ollama API settings updated": "Ollama-API-Einstellungen aktualisiert", - "Ollama Cloud API Key": "", + "Ollama Cloud API Key": "Ollama Cloud API-Schlüssel", "Ollama Version": "Ollama-Version", "On": "Ein", "OneDrive": "OneDrive", @@ -1104,10 +1104,10 @@ "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! Sie verwenden eine nicht unterstützte Methode (nur Frontend). Bitte stellen Sie die WebUI vom Backend bereit.", "Open file": "Datei öffnen", "Open in full screen": "Im Vollbildmodus öffnen", - "Open link": "", + "Open link": "Link öffnen", "Open modal to configure connection": "Modal öffnen, um die Verbindung zu konfigurieren", "Open Modal To Manage Floating Quick Actions": "Modal öffnen, um Schnellaktionen zu verwalten", - "Open Modal To Manage Image Compression": "", + "Open Modal To Manage Image Compression": "Modal öffnen, um die Bildkompression zu verwalten", "Open new chat": "Neuen Chat öffnen", "Open Sidebar": "Seitenleiste öffnen", "Open User Profile Menu": "Benutzerprofilmenü öffnen", @@ -1178,13 +1178,13 @@ "Please do not close the settings page while loading the model.": "Bitte schließen die Einstellungen-Seite nicht, während das Modell lädt.", "Please enter a message or attach a file.": "Bitte geben Sie eine Nachricht ein oder hängen Sie eine Datei an.", "Please enter a prompt": "Bitte geben Sie einen Prompt ein", - "Please enter a valid ID": "", + "Please enter a valid ID": "Bitte geben Sie eine gültige ID ein", "Please enter a valid path": "Bitte geben Sie einen gültigen Pfad ein", "Please enter a valid URL": "Bitte geben Sie eine gültige URL ein", - "Please enter a valid URL.": "", + "Please enter a valid URL.": "Bitte geben Sie eine gültige URL ein", "Please fill in all fields.": "Bitte füllen Sie alle Felder aus.", - "Please register the OAuth client": "", - "Please save the connection to persist the OAuth client information and do not change the ID": "", + "Please register the OAuth client": "Bitte registrieren Sie den OAuth-Client", + "Please save the connection to persist the OAuth client information and do not change the ID": "Bitte speichern Sie die Verbindung, um die OAuth-Clientinformationen zu persistieren, und ändern Sie die ID nicht", "Please select a model first.": "Bitte wählen Sie zuerst ein Modell aus.", "Please select a model.": "Bitte wählen Sie ein Modell aus.", "Please select a reason": "Bitte wählen Sie einen Grund aus", @@ -1224,7 +1224,7 @@ "Re-rank models by topic similarity": "Modelle nach thematischer Ähnlichkeit neu ordnen", "Read": "Lesen", "Read Aloud": "Vorlesen", - "Read more →": "", + "Read more →": "Mehr lesen →", "Reason": "Nachdenken", "Reasoning Effort": "Reasoning Effort", "Reasoning Tags": "Reasoning Tags", @@ -1233,15 +1233,15 @@ "Redirecting you to Open WebUI Community": "Sie werden zur OpenWebUI-Community weitergeleitet", "Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative.": "Verringert die Wahrscheinlichkeit, Unsinn zu generieren. Ein höherer Wert (z.\u202fB. 100) führt zu vielfältigeren Antworten, während ein niedrigerer Wert (z.\u202fB. 10) konservativer ist.", "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Beziehen Sie sich auf sich selbst als \"Benutzer\" (z. B. \"Benutzer lernt Spanisch\")", - "Reference Chats": "", + "Reference Chats": "Chats referenzieren", "Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen", "Regenerate": "Neu generieren", "Regenerate Menu": "Menü neu generieren", - "Register Again": "", - "Register Client": "", - "Registered": "", - "Registration failed": "", - "Registration successful": "", + "Register Again": "Erneut registrieren", + "Register Client": "Client registrieren", + "Registered": "Angemeldet", + "Registration failed": "Registrierung fehlgeschlagen", + "Registration successful": "Registrierung erfolgreich", "Reindex": "Neu indexieren", "Reindex Knowledge Base Vectors": "Vektoren der Wissensdatenbank neu indizieren", "Release Notes": "Veröffentlichungshinweise", @@ -1259,7 +1259,7 @@ "Rename": "Umbenennen", "Reorder Models": "Modelle neu anordnen", "Reply in Thread": "Im Thread antworten", - "Reply to thread...": "", + "Reply to thread...": "Im Thread antworten...", "required": "benötigt", "Reranking Engine": "Reranking-Engine", "Reranking Model": "Reranking-Modell", @@ -1551,7 +1551,7 @@ "To select toolkits here, add them to the \"Tools\" workspace first.": "Um Toolkits auszuwählen, fügen Sie sie zunächst dem Arbeitsbereich \"Werkzeuge\" hinzu.", "Toast notifications for new updates": "Toast-Benachrichtigungen für neue Updates", "Today": "Heute", - "Today at {{LOCALIZED_TIME}}": "", + "Today at {{LOCALIZED_TIME}}": "Heute um {{LOCALIZED_TIME}}", "Toggle search": "Suche umschalten", "Toggle settings": "Einstellungen umschalten", "Toggle sidebar": "Seitenleiste umschalten", @@ -1582,7 +1582,7 @@ "TTS Settings": "TTS-Einstellungen", "TTS Voice": "TTS-Stimme", "Type": "Art", - "Type here...": "", + "Type here...": "Hier eingeben...", "Type Hugging Face Resolve (Download) URL": "Geben Sie die Hugging Face Resolve-URL ein", "Uh-oh! There was an issue with the response.": "Oh nein! Es gab ein Problem mit der Antwort.", "UI": "Oberfläche", @@ -1632,8 +1632,8 @@ "User Webhooks": "Benutzer Webhooks", "Username": "Benutzername", "Users": "Benutzer", - "Uses DefaultAzureCredential to authenticate": "", - "Uses OAuth 2.1 Dynamic Client Registration": "", + "Uses DefaultAzureCredential to authenticate": "Verwendet DefaultAzureCredential zur Authentifizierung", + "Uses OAuth 2.1 Dynamic Client Registration": "Verwendet OAuth 2.1 Dynamic Client Registration", "Using Entire Document": "Verwendung des gesamten Dokuments", "Using Focused Retrieval": "Verwendung relevanter Abschnitte", "Using the default arena model with all models. Click the plus button to add custom models.": "Verwendung des Standard-Arena-Modells mit allen Modellen. Klicken Sie auf die Plus-Schaltfläche, um benutzerdefinierte Modelle hinzuzufügen.", @@ -1668,7 +1668,7 @@ "Web Search in Chat": "Websuche im Chat", "Web Search Query Generation": "Abfragegenerierung für Websuche", "Webhook URL": "Webhook URL", - "Webpage URL": "", + "Webpage URL": "Webseiten-URL", "WebUI Settings": "WebUI-Einstellungen", "WebUI URL": "WebUI-URL", "WebUI will make requests to \"{{url}}\"": "WebUI wird Anfragen an \"{{url}}\" senden", @@ -1697,14 +1697,14 @@ "Yacy Password": "Yacy-Passwort", "Yacy Username": "Yacy-Benutzername", "Yesterday": "Gestern", - "Yesterday at {{LOCALIZED_TIME}}": "", + "Yesterday at {{LOCALIZED_TIME}}": "Gestern um {{LOCALIZED_TIME}}", "You": "Sie", "You are currently using a trial license. Please contact support to upgrade your license.": "Sie benutzen zurzeit eine Testlizenz. Bitte kontaktieren Sie den Support für ein Lizenzupgrade.", "You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Sie können nur mit maximal {{maxCount}} Datei(en) gleichzeitig chatten.", "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Personalisieren Sie Interaktionen mit LLMs, indem Sie über die Schaltfläche \"Verwalten\" Erinnerungen hinzufügen.", "You cannot upload an empty file.": "Sie können keine leere Datei hochladen.", - "You do not have permission to send messages in this channel.": "", - "You do not have permission to send messages in this thread.": "", + "You do not have permission to send messages in this channel.": "Sie haben keine Berechtigung, Nachrichten in diesem Kanal zu senden.", + "You do not have permission to send messages in this thread.": "Sie haben keine Berechtigung, Nachrichten in diesem Thread zu senden.", "You do not have permission to upload files.": "Sie haben keine Berechtigung zum Hochladen von Dateien.", "You have no archived conversations.": "Sie haben keine Chats archiviert.", "You have shared this chat": "Sie haben diesen Chat geteilt", From 0431ad9cc4cf1d5453d27e053ed54bc097017003 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 14:34:26 -0500 Subject: [PATCH 10/84] refac: get_discovery_urls --- backend/open_webui/utils/oauth.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 93992418538..6cf91e3f12b 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -198,13 +198,25 @@ def get_parsed_and_base_url(server_url) -> tuple[urllib.parse.ParseResult, str]: def get_discovery_urls(server_url) -> list[str]: - urls = [] parsed, base_url = get_parsed_and_base_url(server_url) - urls.append( - urllib.parse.urljoin(base_url, "/.well-known/oauth-authorization-server") - ) - urls.append(urllib.parse.urljoin(base_url, "/.well-known/openid-configuration")) + urls = [ + urllib.parse.urljoin(base_url, "/.well-known/oauth-authorization-server"), + urllib.parse.urljoin(base_url, "/.well-known/openid-configuration"), + ] + + if parsed.path and parsed.path != "/": + urls.append( + urllib.parse.urljoin( + base_url, + f"/.well-known/oauth-authorization-server{parsed.path.rstrip('/')}", + ) + ) + urls.append( + urllib.parse.urljoin( + base_url, f"/.well-known/openid-configuration{parsed.path.rstrip('/')}" + ) + ) return urls From ac0852938026ac76f8cedc29968f5f1487303c25 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 14:40:30 -0500 Subject: [PATCH 11/84] refac --- backend/open_webui/routers/configs.py | 59 ++++++++++++++------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index d4b88032e2b..f19fbeedd00 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -207,38 +207,39 @@ async def verify_tool_servers_config( if form_data.type == "mcp": if form_data.auth_type == "oauth_2.1": discovery_urls = get_discovery_urls(form_data.url) - async with aiohttp.ClientSession() as session: - async with session.get( - discovery_urls[0] - ) as oauth_server_metadata_response: - if oauth_server_metadata_response.status != 200: - raise HTTPException( - status_code=400, - detail=f"Failed to fetch OAuth 2.1 discovery document from {discovery_urls[0]}", - ) - - try: - oauth_server_metadata = OAuthMetadata.model_validate( - await oauth_server_metadata_response.json() - ) - return { - "status": True, - "oauth_server_metadata": oauth_server_metadata.model_dump( - mode="json" - ), - } - except Exception as e: - log.info( - f"Failed to parse OAuth 2.1 discovery document: {e}" - ) - raise HTTPException( - status_code=400, - detail=f"Failed to parse OAuth 2.1 discovery document from {discovery_urls[0]}", - ) + for discovery_url in discovery_urls: + log.debug( + f"Trying to fetch OAuth 2.1 discovery document from {discovery_url}" + ) + async with aiohttp.ClientSession() as session: + async with session.get( + discovery_urls[0] + ) as oauth_server_metadata_response: + if oauth_server_metadata_response.status == 200: + try: + oauth_server_metadata = ( + OAuthMetadata.model_validate( + await oauth_server_metadata_response.json() + ) + ) + return { + "status": True, + "oauth_server_metadata": oauth_server_metadata.model_dump( + mode="json" + ), + } + except Exception as e: + log.info( + f"Failed to parse OAuth 2.1 discovery document: {e}" + ) + raise HTTPException( + status_code=400, + detail=f"Failed to parse OAuth 2.1 discovery document from {discovery_urls[0]}", + ) raise HTTPException( status_code=400, - detail=f"Failed to fetch OAuth 2.1 discovery document from {discovery_urls[0]}", + detail=f"Failed to fetch OAuth 2.1 discovery document from {discovery_urls}", ) else: try: From 99d7773230743f7de5acae5bffbc4bbf5f2d2127 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 15:00:06 -0500 Subject: [PATCH 12/84] refac: styling --- .../(app)/admin/functions/create/+page.svelte | 22 ++++++++++--------- .../(app)/admin/functions/edit/+page.svelte | 22 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/routes/(app)/admin/functions/create/+page.svelte b/src/routes/(app)/admin/functions/create/+page.svelte index ff485b45f58..f7d2a780405 100644 --- a/src/routes/(app)/admin/functions/create/+page.svelte +++ b/src/routes/(app)/admin/functions/create/+page.svelte @@ -91,15 +91,17 @@ {#if mounted} {#key func?.content} - { - saveHandler(value); - }} - /> +
+ { + saveHandler(value); + }} + /> +
{/key} {/if} diff --git a/src/routes/(app)/admin/functions/edit/+page.svelte b/src/routes/(app)/admin/functions/edit/+page.svelte index f2b7510387d..4c998679313 100644 --- a/src/routes/(app)/admin/functions/edit/+page.svelte +++ b/src/routes/(app)/admin/functions/edit/+page.svelte @@ -76,16 +76,18 @@ {#if func} - { - saveHandler(value); - }} - /> +
+ { + saveHandler(value); + }} + /> +
{:else}
From 680783266c1c6070a5e771c49797b094acbefbd3 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 15:42:17 -0500 Subject: [PATCH 13/84] refac/fix: tool response header type check --- backend/open_webui/utils/middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index ff8c2156078..0983e87b7f1 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2555,7 +2555,9 @@ async def flush_pending_delta_data(threshold: int = 0): ): tool_result, tool_response_headers = tool_result - if tool_response_headers: + if tool_response_headers and isinstance( + tool_response_headers, dict + ): content_disposition = tool_response_headers.get( "Content-Disposition", tool_response_headers.get( From 41e4e7395c1ba9ae0f4098bca72e88ced6c0b0e1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:48:01 +0000 Subject: [PATCH 14/84] feat: add permission toggle for public sharing of notes This commit introduces a new permission toggle that allows administrators to control whether users can publicly share their notes. - Adds a new environment variable `USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING` to control the default setting. - Adds a `public_notes` permission to the `sharing` section of the user permissions. - Adds a toggle switch to the admin panel for managing this permission. - Implements backend logic to enforce the permission when a user attempts to share a note publicly. --- backend/open_webui/config.py | 6 ++++++ backend/open_webui/routers/notes.py | 12 ++++++++++++ backend/open_webui/routers/users.py | 1 + .../components/admin/Users/Groups/Permissions.svelte | 10 +++++++++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 7e5c35a4512..bf6665f3ccf 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1217,6 +1217,11 @@ def feishu_oauth_register(client: OAuth): == "true" ) +USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = ( + os.environ.get("USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING", "False").lower() + == "true" +) + USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING = ( os.environ.get( "USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING", "False" @@ -1354,6 +1359,7 @@ def feishu_oauth_register(client: OAuth): "public_knowledge": USER_PERMISSIONS_WORKSPACE_KNOWLEDGE_ALLOW_PUBLIC_SHARING, "public_prompts": USER_PERMISSIONS_WORKSPACE_PROMPTS_ALLOW_PUBLIC_SHARING, "public_tools": USER_PERMISSIONS_WORKSPACE_TOOLS_ALLOW_PUBLIC_SHARING, + "public_notes": USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING, }, "chat": { "controls": USER_PERMISSIONS_CHAT_CONTROLS, diff --git a/backend/open_webui/routers/notes.py b/backend/open_webui/routers/notes.py index 0c420e4f12e..3858c4670f2 100644 --- a/backend/open_webui/routers/notes.py +++ b/backend/open_webui/routers/notes.py @@ -180,6 +180,18 @@ async def update_note_by_id( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() ) + # Check if user can share publicly + if ( + user.role != "admin" + and form_data.access_control == None + and not has_permission( + user.id, + "sharing.public_notes", + request.app.state.config.USER_PERMISSIONS, + ) + ): + form_data.access_control = {} + try: note = Notes.update_note_by_id(id, form_data) await sio.emit( diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 9a0f8c6aaf6..2dd229eeb77 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -157,6 +157,7 @@ class SharingPermissions(BaseModel): public_knowledge: bool = True public_prompts: bool = True public_tools: bool = True + public_notes: bool = True class ChatPermissions(BaseModel): diff --git a/src/lib/components/admin/Users/Groups/Permissions.svelte b/src/lib/components/admin/Users/Groups/Permissions.svelte index af0dc1f97fa..b7f7c3093f0 100644 --- a/src/lib/components/admin/Users/Groups/Permissions.svelte +++ b/src/lib/components/admin/Users/Groups/Permissions.svelte @@ -17,7 +17,8 @@ public_models: false, public_knowledge: false, public_prompts: false, - public_tools: false + public_tools: false, + public_notes: false }, chat: { controls: true, @@ -247,6 +248,13 @@
+ +
+
+ {$i18n.t('Notes Public Sharing')} +
+ +

From cda4c95c860c090d4f0bbe1eb03373f0e6169a27 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 15:50:16 -0500 Subject: [PATCH 15/84] fix: default tool calling --- backend/open_webui/utils/middleware.py | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 0983e87b7f1..aaa83026793 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -259,6 +259,85 @@ async def tool_call_handler(tool_call): except Exception as e: tool_result = str(e) + tool_result_embeds = [] + if isinstance(tool_result, HTMLResponse): + content_disposition = tool_result.headers.get( + "Content-Disposition", "" + ) + if "inline" in content_disposition: + content = tool_result.body.decode("utf-8") + tool_result_embeds.append(content) + + if 200 <= tool_result.status_code < 300: + tool_result = { + "status": "success", + "code": "ui_component", + "message": "Embedded UI result is active and visible to the user.", + } + elif 400 <= tool_result.status_code < 500: + tool_result = { + "status": "error", + "code": "ui_component", + "message": f"Client error {tool_result.status_code} from embedded UI result.", + } + elif 500 <= tool_result.status_code < 600: + tool_result = { + "status": "error", + "code": "ui_component", + "message": f"Server error {tool_result.status_code} from embedded UI result.", + } + else: + tool_result = { + "status": "error", + "code": "ui_component", + "message": f"Unexpected status code {tool_result.status_code} from embedded UI result.", + } + else: + tool_result = tool_result.body.decode("utf-8") + + elif ( + tool.get("type") == "external" and isinstance(tool_result, tuple) + ) or ( + tool.get("direct", True) + and isinstance(tool_result, list) + and len(tool_result) == 2 + ): + tool_result, tool_response_headers = tool_result + + if tool_response_headers and isinstance( + tool_response_headers, dict + ): + content_disposition = tool_response_headers.get( + "Content-Disposition", + tool_response_headers.get("content-disposition", ""), + ) + + if "inline" in content_disposition: + content_type = tool_response_headers.get( + "Content-Type", + tool_response_headers.get("content-type", ""), + ) + location = tool_response_headers.get( + "Location", + tool_response_headers.get("location", ""), + ) + + if "text/html" in content_type: + # Display as iframe embed + tool_result_embeds.append(tool_result) + tool_result = { + "status": "success", + "code": "ui_component", + "message": "Embedded UI result is active and visible to the user.", + } + elif location: + tool_result_embeds.append(location) + tool_result = { + "status": "success", + "code": "ui_component", + "message": "Embedded UI result is active and visible to the user.", + } + tool_result_files = [] if isinstance(tool_result, list): for item in tool_result: From 4997ef2662ca12bf9514c0aff0b91a501cba322c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 15:57:03 -0500 Subject: [PATCH 16/84] refac --- backend/open_webui/utils/middleware.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index aaa83026793..0a7f3517435 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -304,6 +304,13 @@ async def tool_call_handler(tool_call): ): tool_result, tool_response_headers = tool_result + try: + if not isinstance(tool_response_headers, dict): + tool_response_headers = dict(tool_response_headers) + except Exception as e: + tool_response_headers = {} + log.debug(e) + if tool_response_headers and isinstance( tool_response_headers, dict ): @@ -2634,6 +2641,16 @@ async def flush_pending_delta_data(threshold: int = 0): ): tool_result, tool_response_headers = tool_result + try: + if not isinstance(tool_response_headers, dict): + tool_response_headers = dict(tool_response_headers) + except Exception as e: + tool_response_headers = {} + log.debug(e) + + print(tool_response_headers) + print(type(tool_response_headers)) + if tool_response_headers and isinstance( tool_response_headers, dict ): From b8c3e5ed3e569c9cff4ca4d15ce0e4599b916029 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 16:43:12 -0500 Subject: [PATCH 17/84] refac --- src/lib/components/admin/Settings/Evaluations/Model.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/admin/Settings/Evaluations/Model.svelte b/src/lib/components/admin/Settings/Evaluations/Model.svelte index ad693f4ec1e..6de56a3ff64 100644 --- a/src/lib/components/admin/Settings/Evaluations/Model.svelte +++ b/src/lib/components/admin/Settings/Evaluations/Model.svelte @@ -34,7 +34,7 @@
-
+
{model.name}
From a1829f6a3e03892dfba7e6d27e97d4160dcad389 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 16:55:38 -0500 Subject: [PATCH 18/84] refac: styling --- .../chat/MessageInput/IntegrationsMenu.svelte | 24 +++---------------- src/lib/components/icons/Knobs.svelte | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 src/lib/components/icons/Knobs.svelte diff --git a/src/lib/components/chat/MessageInput/IntegrationsMenu.svelte b/src/lib/components/chat/MessageInput/IntegrationsMenu.svelte index 698d1985e17..62864c0540f 100644 --- a/src/lib/components/chat/MessageInput/IntegrationsMenu.svelte +++ b/src/lib/components/chat/MessageInput/IntegrationsMenu.svelte @@ -6,8 +6,10 @@ import { config, user, tools as _tools, mobile, settings, toolServers } from '$lib/stores'; + import { getOAuthClientAuthorizationUrl } from '$lib/apis/configs'; import { getTools } from '$lib/apis/tools'; + import Knobs from '$lib/components/icons/Knobs.svelte'; import Dropdown from '$lib/components/common/Dropdown.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import Switch from '$lib/components/common/Switch.svelte'; @@ -20,8 +22,6 @@ import ChevronRight from '$lib/components/icons/ChevronRight.svelte'; import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte'; import ValvesModal from '$lib/components/workspace/common/ValvesModal.svelte'; - import { getOAuthClientAuthorizationUrl } from '$lib/apis/configs'; - import { partition } from 'd3-hierarchy'; const i18n = getContext('i18n'); @@ -369,25 +369,7 @@ showValvesModal = true; }} > - - - - +
diff --git a/src/lib/components/icons/Knobs.svelte b/src/lib/components/icons/Knobs.svelte new file mode 100644 index 00000000000..ab56af55f81 --- /dev/null +++ b/src/lib/components/icons/Knobs.svelte @@ -0,0 +1,24 @@ + + + From 16cf973ce562c9efe86b1363faceb0ca8f999135 Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Fri, 26 Sep 2025 18:25:17 -0400 Subject: [PATCH 19/84] fix: truncate long usernames in UI Long usernames were causing layout issues in several parts of the application. This change truncates long usernames with an ellipsis to prevent them from overflowing. The following areas have been fixed: - Edit User modal - User Chats modal - Edit User Group modal - Users table in the admin overview fix: truncate long usernames in UI Long usernames were causing layout issues in several parts of the application. This change truncates long usernames with an ellipsis to prevent them from overflowing. The following areas have been fixed: - Edit User modal - User Chats modal - Edit User Group modal - Users table in the admin overview Revert "fix: truncate long usernames in UI" This reverts commit b623fdc95d0c494228b49f9369db3bbb3042cef0. --- src/lib/components/admin/Users/Groups/Users.svelte | 4 ++-- src/lib/components/admin/Users/UserList.svelte | 8 ++++---- .../components/admin/Users/UserList/EditUserModal.svelte | 4 ++-- .../components/admin/Users/UserList/UserChatsModal.svelte | 4 +++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/components/admin/Users/Groups/Users.svelte b/src/lib/components/admin/Users/Groups/Users.svelte index 9d13fa45f0d..2cc75a30d3d 100644 --- a/src/lib/components/admin/Users/Groups/Users.svelte +++ b/src/lib/components/admin/Users/Groups/Users.svelte @@ -75,10 +75,10 @@ />
-
+
-
{user.name}
+
{user.name}
diff --git a/src/lib/components/admin/Users/UserList.svelte b/src/lib/components/admin/Users/UserList.svelte index ebb3687e7d8..12c9200afc3 100644 --- a/src/lib/components/admin/Users/UserList.svelte +++ b/src/lib/components/admin/Users/UserList.svelte @@ -383,10 +383,10 @@ /> - -
+ +
user -
{user.name}
+
{user.name}
{user.email} diff --git a/src/lib/components/admin/Users/UserList/EditUserModal.svelte b/src/lib/components/admin/Users/UserList/EditUserModal.svelte index 51d9f0eb3b1..ba4e0d99517 100644 --- a/src/lib/components/admin/Users/UserList/EditUserModal.svelte +++ b/src/lib/components/admin/Users/UserList/EditUserModal.svelte @@ -92,8 +92,8 @@ />
-
-
{selectedUser.name}
+
+
{selectedUser.name}
{$i18n.t('Created at')} diff --git a/src/lib/components/admin/Users/UserList/UserChatsModal.svelte b/src/lib/components/admin/Users/UserList/UserChatsModal.svelte index 1e17dfa3ad7..480f7ccf56a 100644 --- a/src/lib/components/admin/Users/UserList/UserChatsModal.svelte +++ b/src/lib/components/admin/Users/UserList/UserChatsModal.svelte @@ -107,7 +107,9 @@ bind:query bind:orderBy bind:direction - title={$i18n.t("{{user}}'s Chats", { user: user.name })} + title={$i18n.t("{{user}}'s Chats", { + user: user.name.length > 32 ? `${user.name.slice(0, 32)}...` : user.name + })} emptyPlaceholder={$i18n.t('No chats found for this user.')} shareUrl={true} {chatList} From b77848244b578843649c48ebe2e8797fb3564845 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 17:49:42 -0500 Subject: [PATCH 20/84] refac: user valves --- backend/open_webui/functions.py | 6 +++ backend/open_webui/utils/models.py | 1 + src/lib/components/chat/MessageInput.svelte | 40 +++++++++++++++++ .../chat/MessageInput/IntegrationsMenu.svelte | 45 +++++++++++-------- src/lib/components/chat/Navbar.svelte | 5 ++- src/lib/components/icons/Knobs.svelte | 12 ++--- .../(app)/admin/functions/create/+page.svelte | 2 +- .../(app)/admin/functions/edit/+page.svelte | 2 +- 8 files changed, 84 insertions(+), 29 deletions(-) diff --git a/backend/open_webui/functions.py b/backend/open_webui/functions.py index d102263cb34..316efe18e7f 100644 --- a/backend/open_webui/functions.py +++ b/backend/open_webui/functions.py @@ -86,6 +86,10 @@ async def get_function_models(request): try: function_module = get_function_module_by_id(request, pipe.id) + has_user_valves = False + if hasattr(function_module, "UserValves"): + has_user_valves = True + # Check if function is a manifold if hasattr(function_module, "pipes"): sub_pipes = [] @@ -124,6 +128,7 @@ async def get_function_models(request): "created": pipe.created_at, "owned_by": "openai", "pipe": pipe_flag, + "has_user_valves": has_user_valves, } ) else: @@ -141,6 +146,7 @@ async def get_function_models(request): "created": pipe.created_at, "owned_by": "openai", "pipe": pipe_flag, + "has_user_valves": has_user_valves, } ) except Exception as e: diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py index 7e69661f567..587e2a2c7de 100644 --- a/backend/open_webui/utils/models.py +++ b/backend/open_webui/utils/models.py @@ -263,6 +263,7 @@ def get_filter_items_from_module(function, module): "icon": function.meta.manifest.get("icon_url", None) or getattr(module, "icon_url", None) or getattr(module, "icon", None), + "has_user_valves": hasattr(module, "UserValves"), } ] diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index c4b2d4520db..4bcdbc30caa 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -78,6 +78,8 @@ import { getSuggestionRenderer } from '../common/RichTextInput/suggestions'; import CommandSuggestionList from './MessageInput/CommandSuggestionList.svelte'; + import Knobs from '../icons/Knobs.svelte'; + import ValvesModal from '../workspace/common/ValvesModal.svelte'; const i18n = getContext('i18n'); @@ -112,6 +114,10 @@ let inputVariables = {}; let inputVariableValues = {}; + let showValvesModal = false; + let selectedValvesType = 'tool'; // 'tool' or 'function' + let selectedValvesItemId = null; + $: onChange({ prompt, files: files @@ -932,6 +938,16 @@ onSave={inputVariablesModalCallback} /> + { + await tick(); + }} +/> + {#if loaded}
@@ -1449,6 +1465,12 @@ bind:webSearchEnabled bind:imageGenerationEnabled bind:codeInterpreterEnabled + onShowValves={(e) => { + const { type, id } = e; + selectedValvesType = type; + selectedValvesItemId = id; + showValvesModal = true; + }} onClose={async () => { await tick(); @@ -1465,6 +1487,24 @@ {/if} + {#if selectedModelIds.length === 1 && $models.find((m) => m.id === selectedModelIds[0])?.has_user_valves} +
+ + + +
+ {/if} +
{#if (selectedToolIds ?? []).length > 0} - { - await tick(); - }} -/> - { @@ -192,6 +177,27 @@
+ {#if filter?.has_user_valves} +
+ + + +
+ {/if} +
{ e.stopPropagation(); e.preventDefault(); - selectedValvesType = 'tool'; - selectedValvesItemId = toolId; - showValvesModal = true; + onShowValves({ + type: 'tool', + id: toolId + }); }} > diff --git a/src/lib/components/chat/Navbar.svelte b/src/lib/components/chat/Navbar.svelte index 3a2ca32fba3..c8939892ddb 100644 --- a/src/lib/components/chat/Navbar.svelte +++ b/src/lib/components/chat/Navbar.svelte @@ -37,6 +37,7 @@ import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte'; import ChatPlus from '../icons/ChatPlus.svelte'; import ChatCheck from '../icons/ChatCheck.svelte'; + import Knobs from '../icons/Knobs.svelte'; const i18n = getContext('i18n'); @@ -210,7 +211,7 @@ aria-label="Controls" >
- +
@@ -255,7 +256,7 @@
{#if !history.currentId && !$chatId && ($banners.length > 0 || ($config?.license_metadata?.type ?? null) === 'trial' || (($config?.license_metadata?.seats ?? null) !== null && $config?.user_count > $config?.license_metadata?.seats))} -
+
{#if ($config?.license_metadata?.type ?? null) === 'trial'} - - - + + + - - - + + + diff --git a/src/routes/(app)/admin/functions/create/+page.svelte b/src/routes/(app)/admin/functions/create/+page.svelte index f7d2a780405..bb13a759fd9 100644 --- a/src/routes/(app)/admin/functions/create/+page.svelte +++ b/src/routes/(app)/admin/functions/create/+page.svelte @@ -91,7 +91,7 @@ {#if mounted} {#key func?.content} -
+
{#if func} -
+
Date: Fri, 26 Sep 2025 19:01:22 -0500 Subject: [PATCH 21/84] refac: tools --- backend/open_webui/routers/tools.py | 34 ++++++++++++++++------- backend/open_webui/utils/plugin.py | 42 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index eb66a868253..2fa3f6abf61 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -17,7 +17,11 @@ ToolUserResponse, Tools, ) -from open_webui.utils.plugin import load_tool_module_by_id, replace_imports +from open_webui.utils.plugin import ( + load_tool_module_by_id, + replace_imports, + get_tool_module_from_cache, +) from open_webui.utils.tools import get_tool_specs from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.access_control import has_access, has_permission @@ -35,6 +39,14 @@ router = APIRouter() +def get_tool_module(request, tool_id, load_from_db=True): + """ + Get the tool module by its ID. + """ + tool_module, _ = get_tool_module_from_cache(request, tool_id, load_from_db) + return tool_module + + ############################ # GetTools ############################ @@ -42,15 +54,19 @@ @router.get("/", response_model=list[ToolUserResponse]) async def get_tools(request: Request, user=Depends(get_verified_user)): - tools = [ - ToolUserResponse( - **{ - **tool.model_dump(), - "has_user_valves": "class UserValves(BaseModel):" in tool.content, - } + tools = [] + + # Local Tools + for tool in Tools.get_tools(): + tool_module = get_tool_module(request, tool.id) + tools.append( + ToolUserResponse( + **{ + **tool.model_dump(), + "has_user_valves": hasattr(tool_module, "UserValves"), + } + ) ) - for tool in Tools.get_tools() - ] # OpenAPI Tool Servers for server in await get_tool_servers(request): diff --git a/backend/open_webui/utils/plugin.py b/backend/open_webui/utils/plugin.py index 8d9729bae2c..51c3f4f5f7f 100644 --- a/backend/open_webui/utils/plugin.py +++ b/backend/open_webui/utils/plugin.py @@ -166,6 +166,48 @@ def load_function_module_by_id(function_id: str, content: str | None = None): os.unlink(temp_file.name) +def get_tool_module_from_cache(request, tool_id, load_from_db=True): + if load_from_db: + # Always load from the database by default + tool = Tools.get_tool_by_id(tool_id) + if not tool: + raise Exception(f"Tool not found: {tool_id}") + content = tool.content + + new_content = replace_imports(content) + if new_content != content: + content = new_content + # Update the tool content in the database + Tools.update_tool_by_id(tool_id, {"content": content}) + + if ( + hasattr(request.app.state, "TOOL_CONTENTS") + and tool_id in request.app.state.TOOL_CONTENTS + ) and ( + hasattr(request.app.state, "TOOLS") and tool_id in request.app.state.TOOLS + ): + if request.app.state.TOOL_CONTENTS[tool_id] == content: + return request.app.state.TOOLS[tool_id], None + + tool_module, frontmatter = load_tool_module_by_id(tool_id, content) + else: + if hasattr(request.app.state, "TOOLS") and tool_id in request.app.state.TOOLS: + return request.app.state.TOOLS[tool_id], None + + tool_module, frontmatter = load_tool_module_by_id(tool_id) + + if not hasattr(request.app.state, "TOOLS"): + request.app.state.TOOLS = {} + + if not hasattr(request.app.state, "TOOL_CONTENTS"): + request.app.state.TOOL_CONTENTS = {} + + request.app.state.TOOLS[tool_id] = tool_module + request.app.state.TOOL_CONTENTS[tool_id] = content + + return tool_module, frontmatter + + def get_function_module_from_cache(request, function_id, load_from_db=True): if load_from_db: # Always load from the database by default From c80bb3196883e692374abdf4b405ad3da68f139b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 20:48:17 -0500 Subject: [PATCH 22/84] refac/enh: folder optimization --- backend/open_webui/models/chats.py | 7 +- backend/open_webui/models/folders.py | 14 ++++ backend/open_webui/routers/chats.py | 22 ++++++ backend/open_webui/routers/folders.py | 11 +-- src/lib/apis/chats/index.ts | 39 +++++++++++ .../components/layout/Sidebar/ChatItem.svelte | 10 ++- .../components/layout/Sidebar/Folders.svelte | 12 ++++ .../layout/Sidebar/Folders/FolderModal.svelte | 2 - .../layout/Sidebar/RecursiveFolder.svelte | 68 ++++++++++++++----- 9 files changed, 153 insertions(+), 32 deletions(-) diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 97fd9b6256d..083d044053b 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -810,7 +810,7 @@ def get_chats_by_user_id_and_search_text( return [ChatModel.model_validate(chat) for chat in all_chats] def get_chats_by_folder_id_and_user_id( - self, folder_id: str, user_id: str + self, folder_id: str, user_id: str, skip: int = 0, limit: int = 60 ) -> list[ChatModel]: with get_db() as db: query = db.query(Chat).filter_by(folder_id=folder_id, user_id=user_id) @@ -819,6 +819,11 @@ def get_chats_by_folder_id_and_user_id( query = query.order_by(Chat.updated_at.desc()) + if skip: + query = query.offset(skip) + if limit: + query = query.limit(limit) + all_chats = query.all() return [ChatModel.model_validate(chat) for chat in all_chats] diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index c8766457507..45f82470809 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -50,6 +50,20 @@ class FolderModel(BaseModel): model_config = ConfigDict(from_attributes=True) +class FolderMetadataResponse(BaseModel): + icon: Optional[str] = None + + +class FolderNameIdResponse(BaseModel): + id: str + name: str + meta: Optional[FolderMetadataResponse] = None + parent_id: Optional[str] = None + is_expanded: bool = False + created_at: int + updated_at: int + + #################### # Forms #################### diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index 788e355f2b8..65f912ff53a 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -218,6 +218,28 @@ async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user) ] +@router.get("/folder/{folder_id}/list") +async def get_chat_list_by_folder_id( + folder_id: str, page: Optional[int] = 1, user=Depends(get_verified_user) +): + try: + limit = 60 + skip = (page - 1) * limit + + return [ + {"title": chat.title, "id": chat.id, "updated_at": chat.updated_at} + for chat in Chats.get_chats_by_folder_id_and_user_id( + folder_id, user.id, skip=skip, limit=limit + ) + ] + + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() + ) + + ############################ # GetPinnedChats ############################ diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index ddee71ea4df..51c1eba5f4a 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -12,6 +12,7 @@ FolderForm, FolderUpdateForm, FolderModel, + FolderNameIdResponse, Folders, ) from open_webui.models.chats import Chats @@ -44,7 +45,7 @@ ############################ -@router.get("/", response_model=list[FolderModel]) +@router.get("/", response_model=list[FolderNameIdResponse]) async def get_folders(user=Depends(get_verified_user)): folders = Folders.get_folders_by_user_id(user.id) @@ -76,14 +77,6 @@ async def get_folders(user=Depends(get_verified_user)): return [ { **folder.model_dump(), - "items": { - "chats": [ - {"title": chat.title, "id": chat.id, "updated_at": chat.updated_at} - for chat in Chats.get_chats_by_folder_id_and_user_id( - folder.id, user.id - ) - ] - }, } for folder in folders ] diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 59d86007713..a19220278d0 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -327,6 +327,45 @@ export const getChatsByFolderId = async (token: string, folderId: string) => { return res; }; +export const getChatListByFolderId = async (token: string, folderId: string, page: number = 1) => { + let error = null; + + const searchParams = new URLSearchParams(); + if (page !== null) { + searchParams.append('page', `${page}`); + } + + const res = await fetch( + `${WEBUI_API_BASE_URL}/chats/folder/${folderId}/list?${searchParams.toString()}`, + { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + } + ) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const getAllArchivedChats = async (token: string) => { let error = null; diff --git a/src/lib/components/layout/Sidebar/ChatItem.svelte b/src/lib/components/layout/Sidebar/ChatItem.svelte index 20981004573..726455cd6fd 100644 --- a/src/lib/components/layout/Sidebar/ChatItem.svelte +++ b/src/lib/components/layout/Sidebar/ChatItem.svelte @@ -51,6 +51,8 @@ export let selected = false; export let shiftKey = false; + export let onDragEnd = () => {}; + let chat = null; let mouseOver = false; @@ -201,11 +203,13 @@ y = event.clientY; }; - const onDragEnd = (event) => { + const onDragEndHandler = (event) => { event.stopPropagation(); itemElement.style.opacity = '1'; // Reset visual cue after drag dragged = false; + + onDragEnd(event); }; const onClickOutside = (event) => { @@ -225,7 +229,7 @@ // Event listener for when dragging occurs (optional) itemElement.addEventListener('drag', onDrag); // Event listener for when dragging ends - itemElement.addEventListener('dragend', onDragEnd); + itemElement.addEventListener('dragend', onDragEndHandler); } }); @@ -235,7 +239,7 @@ itemElement.removeEventListener('dragstart', onDragStart); itemElement.removeEventListener('drag', onDrag); - itemElement.removeEventListener('dragend', onDragEnd); + itemElement.removeEventListener('dragend', onDragEndHandler); } }); diff --git a/src/lib/components/layout/Sidebar/Folders.svelte b/src/lib/components/layout/Sidebar/Folders.svelte index e4661008a5e..e9ef68fc393 100644 --- a/src/lib/components/layout/Sidebar/Folders.svelte +++ b/src/lib/components/layout/Sidebar/Folders.svelte @@ -18,15 +18,27 @@ sensitivity: 'base' }) ); + + let folderRegistry = {}; + + const onItemMove = (e) => { + console.log(`onItemMove`, e, folderRegistry); + + if (e.originFolderId) { + folderRegistry[e.originFolderId]?.setFolderItems(); + } + }; {#each folderList as folderId (folderId)} { dispatch('import', e.detail); }} diff --git a/src/lib/components/layout/Sidebar/Folders/FolderModal.svelte b/src/lib/components/layout/Sidebar/Folders/FolderModal.svelte index 5f6af176154..a6ec351caaa 100644 --- a/src/lib/components/layout/Sidebar/Folders/FolderModal.svelte +++ b/src/lib/components/layout/Sidebar/Folders/FolderModal.svelte @@ -59,8 +59,6 @@ system_prompt: '', files: [] }; - - console.log(folder); }; const focusInput = async () => { diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte index 7b236e0ed99..497eedd7a18 100644 --- a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte +++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte @@ -8,6 +8,7 @@ import fileSaver from 'file-saver'; const { saveAs } = fileSaver; + import { goto } from '$app/navigation'; import { toast } from 'svelte-sonner'; import { chatId, mobile, selectedFolder, showSidebar } from '$lib/stores'; @@ -21,6 +22,7 @@ import { getChatById, getChatsByFolderId, + getChatListByFolderId, importChat, updateChatFolderIdById } from '$lib/apis/chats'; @@ -37,9 +39,10 @@ import FolderMenu from './Folders/FolderMenu.svelte'; import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; import FolderModal from './Folders/FolderModal.svelte'; - import { goto } from '$app/navigation'; import Emoji from '$lib/components/common/Emoji.svelte'; + import Spinner from '$lib/components/common/Spinner.svelte'; + export let folderRegistry = {}; export let open = false; export let folders; @@ -51,6 +54,7 @@ export let parentDragged = false; export let onDelete = (e) => {}; + export let onItemMove = (e) => {}; let folderElement; @@ -171,6 +175,12 @@ return null; }); + onItemMove({ + originFolderId: chat.folder_id, + targetFolderId: folderId, + e + }); + if (res) { dispatch('update'); } @@ -182,6 +192,7 @@ } } + setFolderItems(); draggedOver = false; } }; @@ -234,6 +245,10 @@ }; onMount(async () => { + folderRegistry[folderId] = { + setFolderItems: () => setFolderItems() + }; + open = folders[folderId].is_expanded; if (folderElement) { folderElement.addEventListener('dragover', onDragOver); @@ -250,7 +265,6 @@ if (folders[folderId]?.new) { delete folders[folderId].new; - await tick(); renameHandler(); } @@ -339,6 +353,21 @@ }, 500); }; + let chats = null; + export const setFolderItems = async () => { + await tick(); + if (open) { + chats = await getChatListByFolderId(localStorage.token, folderId).catch((error) => { + toast.error(`${error}`); + return []; + }); + } else { + chats = null; + } + }; + + $: setFolderItems(open); + const renameHandler = async () => { console.log('Edit'); await tick(); @@ -419,8 +448,6 @@ bind:open className="w-full" buttonClassName="w-full" - hide={(folders[folderId]?.childrenIds ?? []).length === 0 && - (folders[folderId].items?.chats ?? []).length === 0} onChange={(state) => { dispatch('open', state); }} @@ -466,6 +493,7 @@ class="text-gray-500 dark:text-gray-500 transition-all p-1 hover:bg-gray-200 dark:hover:bg-gray-850 rounded-lg" on:click={(e) => { e.stopPropagation(); + e.stopImmediatePropagation(); open = !open; isExpandedUpdateDebounceHandler(); }} @@ -548,7 +576,7 @@
- {#if (folders[folderId]?.childrenIds ?? []).length > 0 || (folders[folderId].items?.chats ?? []).length > 0} + {#if (folders[folderId]?.childrenIds ?? []).length > 0 || (chats ?? []).length > 0}
@@ -564,10 +592,12 @@ {#each children as childFolder (`${folderId}-${childFolder.id}`)} { dispatch('import', e.detail); @@ -582,18 +612,22 @@ {/each} {/if} - {#if folders[folderId].items?.chats} - {#each folders[folderId].items.chats as chat (chat.id)} - { - dispatch('change', e.detail); - }} - /> - {/each} - {/if} + {#each chats ?? [] as chat (chat.id)} + { + dispatch('change', e.detail); + }} + /> + {/each} +
+ {/if} + + {#if chats === null} +
+
{/if}
From a05dab62982291a8a0941329bf070ea00d6906dc Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Fri, 26 Sep 2025 21:16:34 -0500 Subject: [PATCH 23/84] refac --- .../chat/Placeholder/FolderPlaceholder.svelte | 35 +++++++++++++++- .../chat/Placeholder/FolderTitle.svelte | 7 +++- src/lib/components/layout/Sidebar.svelte | 6 +++ .../components/layout/Sidebar/Folders.svelte | 9 ++--- .../layout/Sidebar/Folders/FolderModal.svelte | 36 ++++++++++------- .../layout/Sidebar/RecursiveFolder.svelte | 40 ++++++++++++++----- 6 files changed, 100 insertions(+), 33 deletions(-) diff --git a/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte b/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte index 057dd7018d2..5b5af5970b1 100644 --- a/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte +++ b/src/lib/components/chat/Placeholder/FolderPlaceholder.svelte @@ -1,15 +1,40 @@
@@ -45,7 +70,13 @@ {#if selectedTab === 'knowledge'} {:else if selectedTab === 'chats'} - + {#if chats !== null} + + {:else} +
+ +
+ {/if} {/if}
diff --git a/src/lib/components/chat/Placeholder/FolderTitle.svelte b/src/lib/components/chat/Placeholder/FolderTitle.svelte index bfd72681d38..16a1c428361 100644 --- a/src/lib/components/chat/Placeholder/FolderTitle.svelte +++ b/src/lib/components/chat/Placeholder/FolderTitle.svelte @@ -115,7 +115,12 @@ {#if folder} - + { @@ -922,6 +925,7 @@ }} > { @@ -981,6 +985,8 @@ return null; } ); + + folderRegistry[chat.folder_id]?.setFolderItems(); } if (chat.pinned) { diff --git a/src/lib/components/layout/Sidebar/Folders.svelte b/src/lib/components/layout/Sidebar/Folders.svelte index e9ef68fc393..6c99dc5404b 100644 --- a/src/lib/components/layout/Sidebar/Folders.svelte +++ b/src/lib/components/layout/Sidebar/Folders.svelte @@ -1,8 +1,11 @@ + + From b4eea78aff28a1506794e6eb8f9c32c9a4c08f98 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 27 Sep 2025 04:06:42 -0500 Subject: [PATCH 31/84] refac --- backend/open_webui/models/messages.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 197befa0615..2e70b8a4d74 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -222,7 +222,7 @@ def get_messages_by_channel_id( def get_messages_by_parent_id( self, channel_id: str, parent_id: str, skip: int = 0, limit: int = 50 - ) -> list[MessageModel]: + ) -> list[MessageReplyToResponse]: with get_db() as db: message = db.get(Message, parent_id) @@ -242,7 +242,19 @@ def get_messages_by_parent_id( if len(all_messages) < limit: all_messages.append(message) - return [MessageModel.model_validate(message) for message in all_messages] + return [ + MessageReplyToResponse.model_validate( + { + **MessageModel.model_validate(message).model_dump(), + "reply_to_message": ( + self.get_message_by_id(message.reply_to_id).model_dump() + if message.reply_to_id + else None + ), + } + ) + for message in all_messages + ] def update_message_by_id( self, id: str, form_data: MessageForm From 86ef57f6c3cfb10f614e56418b8ec16e4ae8a475 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 27 Sep 2025 04:33:00 -0500 Subject: [PATCH 32/84] refac --- backend/open_webui/models/messages.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 2e70b8a4d74..0066154cd95 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -135,7 +135,7 @@ def insert_new_message( db.refresh(result) return MessageModel.model_validate(result) if result else None - def get_message_by_id(self, id: str) -> Optional[MessageReplyToResponse]: + def get_message_by_id(self, id: str) -> Optional[MessageResponse]: with get_db() as db: message = db.get(Message, id) if not message: @@ -146,20 +146,22 @@ def get_message_by_id(self, id: str) -> Optional[MessageReplyToResponse]: if message.reply_to_id else None ) + reactions = self.get_reactions_by_message_id(id) - replies = self.get_thread_replies_by_message_id(id) + thread_replies = self.get_thread_replies_by_message_id(id) user = Users.get_user_by_id(message.user_id) - - return MessageReplyToResponse.model_validate( + return MessageResponse.model_validate( { **MessageModel.model_validate(message).model_dump(), "user": user.model_dump() if user else None, "reply_to_message": ( reply_to_message.model_dump() if reply_to_message else None ), - "latest_reply_at": replies[0].created_at if replies else None, - "reply_count": len(replies), + "latest_reply_at": ( + thread_replies[0].created_at if thread_replies else None + ), + "reply_count": len(thread_replies), "reactions": reactions, } ) From 272c6f5ec5cf63ceca6f92303e74f19fc80946c2 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 27 Sep 2025 04:38:54 -0500 Subject: [PATCH 33/84] refac --- backend/open_webui/models/messages.py | 94 +++++++++++++++++---------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 0066154cd95..8b0027b8e78 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -174,19 +174,27 @@ def get_thread_replies_by_message_id(self, id: str) -> list[MessageReplyToRespon .order_by(Message.created_at.desc()) .all() ) - return [ - MessageReplyToResponse.model_validate( - { - **MessageModel.model_validate(message).model_dump(), - "reply_to_message": ( - self.get_message_by_id(message.reply_to_id).model_dump() - if message.reply_to_id - else None - ), - } + + messages = [] + for message in all_messages: + reply_to_message = ( + self.get_message_by_id(message.reply_to_id) + if message.reply_to_id + else None ) - for message in all_messages - ] + messages.append( + MessageReplyToResponse.model_validate( + { + **MessageModel.model_validate(message).model_dump(), + "reply_to_message": ( + reply_to_message.model_dump() + if reply_to_message + else None + ), + } + ) + ) + return messages def get_reply_user_ids_by_message_id(self, id: str) -> list[str]: with get_db() as db: @@ -208,19 +216,26 @@ def get_messages_by_channel_id( .all() ) - return [ - MessageReplyToResponse.model_validate( - { - **MessageModel.model_validate(message).model_dump(), - "reply_to_message": ( - self.get_message_by_id(message.reply_to_id).model_dump() - if message.reply_to_id - else None - ), - } + messages = [] + for message in all_messages: + reply_to_message = ( + self.get_message_by_id(message.reply_to_id) + if message.reply_to_id + else None ) - for message in all_messages - ] + messages.append( + MessageReplyToResponse.model_validate( + { + **MessageModel.model_validate(message).model_dump(), + "reply_to_message": ( + reply_to_message.model_dump() + if reply_to_message + else None + ), + } + ) + ) + return messages def get_messages_by_parent_id( self, channel_id: str, parent_id: str, skip: int = 0, limit: int = 50 @@ -244,19 +259,26 @@ def get_messages_by_parent_id( if len(all_messages) < limit: all_messages.append(message) - return [ - MessageReplyToResponse.model_validate( - { - **MessageModel.model_validate(message).model_dump(), - "reply_to_message": ( - self.get_message_by_id(message.reply_to_id).model_dump() - if message.reply_to_id - else None - ), - } + messages = [] + for message in all_messages: + reply_to_message = ( + self.get_message_by_id(message.reply_to_id) + if message.reply_to_id + else None ) - for message in all_messages - ] + messages.append( + MessageReplyToResponse.model_validate( + { + **MessageModel.model_validate(message).model_dump(), + "reply_to_message": ( + reply_to_message.model_dump() + if reply_to_message + else None + ), + } + ) + ) + return messages def update_message_by_id( self, id: str, form_data: MessageForm From 27cd87e9ada6e5d85d5a142ee08b4c30367357cd Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Sat, 27 Sep 2025 15:45:25 -0400 Subject: [PATCH 34/84] fix: i81n.t and correct button layout issue 1. **i18n Regression:** A latent bug in `src/routes/(app)/workspace/models/create/+page.svelte` was causing an `i18n.t is not a function` error. This was due to an incorrect call to the `i18n` Svelte store. The fix corrects the call to use the proper auto-subscription syntax (`$i18n.t()`). 2. **Vertical Button Text:** In `src/lib/components/playground/Chat.svelte`, the "Assistant"/"User" role button's text was displaying vertically. This was caused by a `flex-1` class on its container, which has been removed. --- src/lib/components/playground/Chat.svelte | 2 +- src/routes/(app)/workspace/models/create/+page.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/playground/Chat.svelte b/src/lib/components/playground/Chat.svelte index 933c9b34b83..5b4d1794291 100644 --- a/src/lib/components/playground/Chat.svelte +++ b/src/lib/components/playground/Chat.svelte @@ -296,7 +296,7 @@
-
+
-
- -
-
-
{ - e.preventDefault(); - submitHandler(); - }} - > -
-
-
-
{$i18n.t('Name')}
- -
- -
-
-
- -
-
{$i18n.t('Description')}
- -
-