From d7147d6cddfd314f0f1be77b15cec406a609ef36 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 11:24:15 -0600 Subject: [PATCH 01/27] refac --- src/lib/components/chat/Messages.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 36c0a2788e..9328f43961 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -67,6 +67,7 @@ messagesLoading = true; messagesCount += 20; + buildMessages(); await tick(); From 64957db7b3474574f7e083f4f306dc5a060faadf Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 11:26:33 -0600 Subject: [PATCH 02/27] refac --- backend/open_webui/utils/middleware.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 55f55ab033..1950148568 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -375,9 +375,9 @@ def serialize_output(output: list) -> str: result_item = tool_outputs.get(call_id) if result_item: result_text = "" - for output in result_item.get("output", []): - if "text" in output: - output_text = output.get("text", "") + for result_output in result_item.get("output", []): + if "text" in result_output: + output_text = result_output.get("text", "") result_text += ( str(output_text) if not isinstance(output_text, str) @@ -4424,7 +4424,8 @@ async def flush_pending_delta_data(threshold: int = 0): code = sanitize_code(code) if CODE_INTERPRETER_BLOCKED_MODULES: - blocking_code = textwrap.dedent(f""" + blocking_code = textwrap.dedent( + f""" import builtins BLOCKED_MODULES = {CODE_INTERPRETER_BLOCKED_MODULES} @@ -4440,7 +4441,8 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0): return _real_import(name, globals, locals, fromlist, level) builtins.__import__ = restricted_import - """) + """ + ) code = blocking_code + "\n" + code if ( From b338850cc1f7da884f395193bdf9c0c880d70de3 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Tue, 3 Mar 2026 01:26:58 +0800 Subject: [PATCH 03/27] Merge pull request #22127 from ShirasawaSama/patch-49 fix: Fix TypeScript syntax compilation errors --- src/lib/components/chat/ChatControls.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/components/chat/ChatControls.svelte b/src/lib/components/chat/ChatControls.svelte index d7f546c228..f9c68c2350 100644 --- a/src/lib/components/chat/ChatControls.svelte +++ b/src/lib/components/chat/ChatControls.svelte @@ -1,4 +1,4 @@ - @@ -58,8 +58,10 @@ let paneReady = false; // Tab state for Controls+Files panel - let activeTab: 'controls' | 'files' | 'overview' = savedTab; - $: savedTab = activeTab; + let activeTab = savedTab; + // svelte-ignore reactive_declaration_module_script_dependency + $: { savedTab = activeTab }; + $: hasMessages = history?.messages && Object.keys(history.messages).length > 0; $: showControlsTab = $user?.role === 'admin' || ($user?.permissions?.chat?.controls ?? true); From c701ebe07bd152eecb42b0bf6de26071358a5c76 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 11:29:29 -0600 Subject: [PATCH 04/27] refac --- src/lib/components/chat/Messages.svelte | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib/components/chat/Messages.svelte b/src/lib/components/chat/Messages.svelte index 9328f43961..dbd8daa16e 100644 --- a/src/lib/components/chat/Messages.svelte +++ b/src/lib/components/chat/Messages.svelte @@ -99,16 +99,21 @@ // Throttle message list rebuilds to once per animation frame during streaming. // Structural changes (currentId change) always rebuild immediately. - $: if (history.currentId) { - const currentIdChanged = history.currentId !== lastCurrentId; - lastCurrentId = history.currentId; + const handleHistoryChange = (currentId, _messages) => { + if (!currentId) { + messages = []; + return; + } + + const currentIdChanged = currentId !== lastCurrentId; + lastCurrentId = currentId; if (currentIdChanged) { // Structural change: new chat, navigation, new message — rebuild immediately cancelAnimationFrame(pendingRebuild); pendingRebuild = null; buildMessages(); - } else if (history.messages) { + } else if (_messages) { // Content update (streaming) — throttle to once per frame if (!pendingRebuild) { pendingRebuild = requestAnimationFrame(() => { @@ -117,9 +122,9 @@ }); } } - } else { - messages = []; - } + }; + + $: handleHistoryChange(history.currentId, history.messages); $: if (autoScroll && bottomPadding) { (async () => { From 0c42cd2c012f9f49816adac897e2b46573b3cb6c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 12:03:23 -0600 Subject: [PATCH 05/27] enh: ot move --- src/lib/apis/terminal/index.ts | 26 ++++++++ src/lib/components/chat/FileNav.svelte | 19 ++++++ .../chat/FileNav/FileEntryRow.svelte | 62 ++++++++++++++++--- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/lib/apis/terminal/index.ts b/src/lib/apis/terminal/index.ts index 5815bcd3c8..74c936af7e 100644 --- a/src/lib/apis/terminal/index.ts +++ b/src/lib/apis/terminal/index.ts @@ -193,3 +193,29 @@ export const setCwd = async ( }); return res; }; + +export const moveEntry = async ( + baseUrl: string, + apiKey: string, + source: string, + destination: string +): Promise<{ source: string; destination: string } | null> => { + const url = `${baseUrl.replace(/\/$/, '')}/files/move`; + const res = await fetch(url, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ source, destination }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error('open-terminal moveEntry error:', err); + return null; + }); + return res; +}; diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index 84ad18a71a..2f70afc179 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -21,6 +21,7 @@ uploadToTerminal, createDirectory, deleteEntry, + moveEntry, setCwd, type FileEntry } from '$lib/apis/terminal'; @@ -323,6 +324,23 @@ showDeleteConfirm = true; }; + // ── Move (drag-and-drop) ──────────────────────────────────────────── + const handleMove = async (source: string, destFolder: string) => { + const terminal = selectedTerminal; + if (!terminal) return; + + const fileName = source.split('/').pop() ?? ''; + const destination = `${destFolder}${fileName}`; + + if (source === destination) return; + + const result = await moveEntry(terminal.url, terminal.key, source, destination); + toast[result ? 'success' : 'error']( + $i18n.t(result ? 'Moved {{name}}' : 'Failed to move {{name}}', { name: fileName }) + ); + await loadDir(currentPath); + }; + // ── Lifecycle ──────────────────────────────────────────────────────── onMount(async () => { const terminal = getTerminal(); @@ -717,6 +735,7 @@ onOpen={openEntry} onDownload={downloadFile} onDelete={requestDelete} + onMove={handleMove} /> {/each} diff --git a/src/lib/components/chat/FileNav/FileEntryRow.svelte b/src/lib/components/chat/FileNav/FileEntryRow.svelte index c5d778555e..f97aa7270e 100644 --- a/src/lib/components/chat/FileNav/FileEntryRow.svelte +++ b/src/lib/components/chat/FileNav/FileEntryRow.svelte @@ -18,24 +18,66 @@ export let onOpen: (entry: FileEntry) => void = () => {}; export let onDownload: (path: string) => void = () => {}; export let onDelete: (path: string, name: string) => void = () => {}; + export let onMove: (source: string, destFolder: string) => void = () => {}; + + let dragOverFolder = false;
  • -
    +
    { + if (entry.type !== 'directory') return; + if (!e.dataTransfer?.types.includes('application/x-terminal-file-move')) return; + e.preventDefault(); + e.stopPropagation(); + dragOverFolder = true; + }} + on:dragleave={(e) => { + if (entry.type !== 'directory') return; + e.stopPropagation(); + dragOverFolder = false; + }} + on:drop={(e) => { + if (entry.type !== 'directory') return; + const raw = e.dataTransfer?.getData('application/x-terminal-file-move'); + if (!raw) return; + e.preventDefault(); + e.stopPropagation(); + dragOverFolder = false; + try { + const data = JSON.parse(raw); + if (data.path) { + const destFolder = `${currentPath}${entry.name}/`; + onMove(data.path, destFolder); + } + } catch {} + }} + > From 395098c6f1b7499d37ad55145a5931431d3e72e9 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 12:07:55 -0600 Subject: [PATCH 07/27] refac --- src/lib/components/chat/FileNav.svelte | 4 ++++ src/lib/components/chat/FileNav/FileEntryRow.svelte | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index d081b98bd3..4fc148bb71 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -334,6 +334,10 @@ if (source === destination) return; + // Prevent moving a folder into itself or its own subtree + const sourceDir = source.endsWith('/') ? source : source + '/'; + if (destFolder.startsWith(sourceDir)) return; + const result = await moveEntry(terminal.url, terminal.key, source, destination); toast[result ? 'success' : 'error']( $i18n.t(result ? 'Moved {{name}}' : 'Failed to move {{name}}', { name: fileName }) diff --git a/src/lib/components/chat/FileNav/FileEntryRow.svelte b/src/lib/components/chat/FileNav/FileEntryRow.svelte index f97aa7270e..6f531b0f01 100644 --- a/src/lib/components/chat/FileNav/FileEntryRow.svelte +++ b/src/lib/components/chat/FileNav/FileEntryRow.svelte @@ -51,6 +51,8 @@ const data = JSON.parse(raw); if (data.path) { const destFolder = `${currentPath}${entry.name}/`; + // Don't allow dropping a folder onto itself + if (data.path + '/' === destFolder || data.path === destFolder) return; onMove(data.path, destFolder); } } catch {} From 11487d66fc1a2dfafbdaa2b7ef939a86caaf3872 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 12:09:49 -0600 Subject: [PATCH 08/27] refac --- src/lib/apis/terminal/index.ts | 4 ++-- src/lib/components/chat/FileNav.svelte | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/apis/terminal/index.ts b/src/lib/apis/terminal/index.ts index 74c936af7e..5ab2039833 100644 --- a/src/lib/apis/terminal/index.ts +++ b/src/lib/apis/terminal/index.ts @@ -199,7 +199,7 @@ export const moveEntry = async ( apiKey: string, source: string, destination: string -): Promise<{ source: string; destination: string } | null> => { +): Promise<{ source: string; destination: string } | { error: string }> => { const url = `${baseUrl.replace(/\/$/, '')}/files/move`; const res = await fetch(url, { method: 'POST', @@ -215,7 +215,7 @@ export const moveEntry = async ( }) .catch((err) => { console.error('open-terminal moveEntry error:', err); - return null; + return { error: err?.detail ?? 'Move failed' }; }); return res; }; diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index 4fc148bb71..8f85f1e504 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -339,9 +339,11 @@ if (destFolder.startsWith(sourceDir)) return; const result = await moveEntry(terminal.url, terminal.key, source, destination); - toast[result ? 'success' : 'error']( - $i18n.t(result ? 'Moved {{name}}' : 'Failed to move {{name}}', { name: fileName }) - ); + if ('error' in result) { + toast.error(result.error); + } else { + toast.success($i18n.t('Moved {{name}}', { name: fileName })); + } await loadDir(currentPath); }; From bec227da302dca93b2525baa71dec3ebb5205fe0 Mon Sep 17 00:00:00 2001 From: Shirasawa <764798966@qq.com> Date: Tue, 3 Mar 2026 02:23:00 +0800 Subject: [PATCH 09/27] i18n: improve Chinese translations (#22148) --- src/lib/i18n/locales/zh-CN/translation.json | 4 ++-- src/lib/i18n/locales/zh-TW/translation.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 04f6678546..776dfd7a63 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -872,7 +872,7 @@ "File Context": "文件上下文", "File deleted successfully.": "文件已成功删除。", "File Mode": "文件模式", - "File name": "", + "File name": "文件名", "File not found.": "文件未找到。", "File removed successfully.": "文件成功删除", "File size should not exceed {{maxSize}} MB.": "文件大小不应超过 {{maxSize}} MB", @@ -1265,7 +1265,7 @@ "New": "最新", "New Button": "新按钮", "New Chat": "新对话", - "New File": "", + "New File": "新建文件", "New Folder": "创建分组", "New Function": "新函数", "New Group": "新建权限组", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index d4618f1630..4850a4477a 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -872,7 +872,7 @@ "File Context": "檔案上下文", "File deleted successfully.": "檔案已成功刪除。", "File Mode": "檔案模式", - "File name": "", + "File name": "檔案名稱", "File not found.": "未找到檔案。", "File removed successfully.": "成功移除檔案。", "File size should not exceed {{maxSize}} MB.": "檔案大小不應超過 {{maxSize}} MB。", @@ -1265,7 +1265,7 @@ "New": "最新", "New Button": "新按鈕", "New Chat": "新增對話", - "New File": "", + "New File": "新增檔案", "New Folder": "新增資料夾", "New Function": "新增函式", "New Group": "新增權限群組", From 3909b62ffcf49839fa57346ed8487ae759811503 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 2 Mar 2026 12:45:50 -0600 Subject: [PATCH 10/27] enh: file nav html rendering --- src/lib/components/chat/FileNav.svelte | 4 +++- src/lib/components/chat/FileNav/FilePreview.svelte | 14 +++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte index 8f85f1e504..07728d1499 100644 --- a/src/lib/components/chat/FileNav.svelte +++ b/src/lib/components/chat/FileNav.svelte @@ -62,10 +62,12 @@ const MD_EXTS = new Set(['md', 'markdown', 'mdx']); const CSV_EXTS = new Set(['csv', 'tsv']); + const HTML_EXTS = new Set(['html', 'htm']); const getFileExt = (path: string | null) => path?.split('.').pop()?.toLowerCase() ?? ''; $: isMarkdown = MD_EXTS.has(getFileExt(selectedFile)); $: isCsv = CSV_EXTS.has(getFileExt(selectedFile)); + $: isHtml = HTML_EXTS.has(getFileExt(selectedFile)); $: isTextFile = fileContent !== null && fileImageUrl === null && filePdfData === null; // ── Upload / folder creation ───────────────────────────────────────── @@ -513,7 +515,7 @@ {/if} - {#if (isMarkdown || isCsv) && fileContent !== null && !editing} + {#if (isMarkdown || isCsv || isHtml) && fileContent !== null && !editing}