From c96e99cd131987c531d0ad100b5d4d50f39e0485 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Mon, 15 Dec 2025 22:34:25 +0800 Subject: [PATCH] fix(ui): load folder contents in monitor and improve messages --- modules/file_manager.py | 4 +- .../chat/monitor/MonitorDirector.ts | 79 +++++++++++++------ .../components/chat/monitor/progressMap.ts | 14 ++-- static/src/utils/chatDisplay.ts | 8 +- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/modules/file_manager.py b/modules/file_manager.py index 411ea82..c4fd612 100644 --- a/modules/file_manager.py +++ b/modules/file_manager.py @@ -158,8 +158,8 @@ class FileManager: if full_path.parent == self.project_path: return { "success": False, - "error": "禁止在项目根目录直接创建文件,请先创建或选择子目录。", - "suggestion": "创建文件所属文件夹,在其中创建新文件。" + "error": "禁止在项目根目录直接创建文件,请先创建或选择子目录(例如 ./data 或 ./docs)。", + "suggestion": "请先创建文件夹,再在该文件夹中创建新文件。" } if self._use_container(): result = self._container_call("create_file", { diff --git a/static/src/components/chat/monitor/MonitorDirector.ts b/static/src/components/chat/monitor/MonitorDirector.ts index 53163f0..1159dc0 100644 --- a/static/src/components/chat/monitor/MonitorDirector.ts +++ b/static/src/components/chat/monitor/MonitorDirector.ts @@ -96,7 +96,7 @@ type EditorOperation = { const EDITOR_MAX_RENDER_LINES = 360; const EDITOR_DIFF_LIMIT = 2000; const EDITOR_MAX_ANIMATION_STEPS = 4000; -const EDITOR_TYPING_THRESHOLD = 4096; +const EDITOR_TYPING_THRESHOLD = 180; const EDITOR_TYPING_INTERVAL = 34; const EDITOR_ERASE_INTERVAL = 26; const MONITOR_EDITOR_DEBUG = false; @@ -777,6 +777,30 @@ export class MonitorDirector implements MonitorDriver { } } + private async loadFolderEntries(folderKey: string) { + const path = folderKey || ''; + try { + const resp = await fetch(`/api/gui/files/entries?path=${encodeURIComponent(path)}`, { + method: 'GET', + credentials: 'include', + headers: { Accept: 'application/json' } + }); + const data = await resp.json().catch(() => null); + if (!data?.success) { + return; + } + const items = Array.isArray(data?.data?.items) ? data.data.items : []; + const entries: FolderEntry[] = items.map((item: any) => ({ + name: item?.name || '', + path: item?.path || this.composePath([path, item?.name].filter(Boolean)), + type: item?.type === 'directory' ? 'folder' : 'file' + })); + this.folderEntries.set(path, entries); + } catch (error) { + console.warn('[MonitorDirector] loadFolderEntries failed', path, error); + } + } + private renderFolderEntries(folderKey: string, animate = true) { this.ensureFolderKey(folderKey); const entries = this.folderEntries.get(folderKey) || []; @@ -840,7 +864,7 @@ export class MonitorDirector implements MonitorDriver { await this.movePointerToElement(icon, { duration: 720 }); await this.click({ count: 2 }); await sleep(180); - this.openFolder(folderName, folderName); + await this.openFolder(folderName, folderName); await sleep(60); } @@ -869,7 +893,7 @@ export class MonitorDirector implements MonitorDriver { return null; } this.upsertFolderEntry(parentKey, { name: filename, type: 'file' }, { animate: false }); - this.openFolder(parentKey, parentKey); + await this.openFolder(parentKey, parentKey); await sleep(40); const entryPath = this.composePath([parentKey, filename]); const entryEl = this.findFolderEntryElement(entryPath); @@ -935,7 +959,7 @@ export class MonitorDirector implements MonitorDriver { if (matches) { currentKey = this.composePath(activeSegments); startIndex = activeSegments.length; - this.openFolder(currentKey, currentKey); + await this.openFolder(currentKey, currentKey); await sleep(40); } } @@ -952,11 +976,11 @@ export class MonitorDirector implements MonitorDriver { const segment = targetSegments[i]; const nextKey = this.composePath([currentKey, segment]); this.upsertFolderEntry(currentKey, { name: segment, type: 'folder' }, { animate: false }); - this.openFolder(currentKey, currentKey); + await this.openFolder(currentKey, currentKey); await sleep(40); await this.doubleClickFolderEntry(nextKey); currentKey = nextKey; - this.openFolder(currentKey, currentKey); + await this.openFolder(currentKey, currentKey); await sleep(60); } return currentKey; @@ -1367,28 +1391,22 @@ export class MonitorDirector implements MonitorDriver { * 根据长度自动选择“逐字符”或“逐行”动画的输入方式。 * 过长内容按行填充,避免动画过慢。 */ - private async typeSmartText(target: HTMLElement, text: string) { + private async typeSmartText(target: HTMLElement, text: string, forceLineMode = false) { const lines = text.split('\n'); - const long = lines.length > 8 || text.length > 400; - const lineDelay = 55; + const long = forceLineMode || lines.length > 2 || text.length > 120; + const lineDelay = 32; if (long) { target.textContent = ''; - lines.forEach((line, idx) => { - target.textContent += (idx > 0 ? '\n' : '') + line; - }); - // 行级动画:逐行显现 - const chunks = target.textContent.split('\n'); - target.textContent = ''; - for (let i = 0; i < chunks.length; i += 1) { - target.textContent += (i > 0 ? '\n' : '') + chunks[i]; - await sleep(lineDelay); + for (let i = 0; i < lines.length; i += 1) { + target.textContent += (i > 0 ? '\n' : '') + lines[i]; + await sleep(Math.max(12, lineDelay - Math.min(i, 6) * 3)); } return; } const chars = Array.from(text); for (const ch of chars) { target.textContent = (target.textContent || '') + ch; - await sleep(26); + await sleep(18); } } @@ -1781,6 +1799,16 @@ export class MonitorDirector implements MonitorDriver { this.renderTerminalHistory(sessionId); const { bodyEl } = this.getTerminalInstance(); const promptEl = bodyEl.lastElementChild as HTMLElement | null; + const long = command.length > 80 || command.split('\n').length > 1; + if (long) { + prompt.text = `➜ ${command}`; + if (promptEl) { + promptEl.textContent = prompt.text; + this.scrollPromptIntoView(this.getTerminalInstance()); + } + await sleep(50); + return; + } let charIndex = 0; const chars = command.split(''); for (const ch of chars) { @@ -1792,7 +1820,7 @@ export class MonitorDirector implements MonitorDriver { if (promptEl && (charIndex % 6 === 0 || charIndex === chars.length)) { this.scrollPromptIntoView(this.getTerminalInstance()); } - await sleep(46); + await sleep(32); } } @@ -2381,7 +2409,7 @@ export class MonitorDirector implements MonitorDriver { const existing = (this.folderEntries.get(parentKey) || []).find(item => item.name === fromName); const entryType = existing?.type || 'file'; this.upsertFolderEntry(parentKey, { name: fromName, type: entryType }, { animate: false }); - this.openFolder(parentKey, parentKey); + await this.openFolder(parentKey, parentKey); await sleep(40); const entryPath = this.composePath([parentKey, fromName]); const entryEl = this.findFolderEntryElement(entryPath); @@ -2436,7 +2464,7 @@ export class MonitorDirector implements MonitorDriver { const existing = (this.folderEntries.get(parentKey) || []).find(item => item.name === name); const entryType = existing?.type || 'file'; this.upsertFolderEntry(parentKey, { name, type: entryType }, { animate: false }); - this.openFolder(parentKey, parentKey); + await this.openFolder(parentKey, parentKey); await sleep(40); const entryPath = this.composePath([parentKey, name]); const entryEl = this.findFolderEntryElement(entryPath); @@ -2899,7 +2927,7 @@ export class MonitorDirector implements MonitorDriver { }; this.sceneHandlers.terminalSession = async (payload, runtime) => { - this.applySceneStatus(runtime, 'terminalSession', '正在查看终端'); + this.applySceneStatus(runtime, 'terminalSession', '打开终端'); const action = (payload?.arguments?.action || payload?.action || '').toLowerCase(); // 特殊处理:如果是关闭/切换终端,不要无意中新建会话 if (action === 'close' || action === 'switch') { @@ -2969,7 +2997,7 @@ export class MonitorDirector implements MonitorDriver { }; this.sceneHandlers.terminalInput = async (payload, runtime) => { - this.applySceneStatus(runtime, 'terminalInput', '正在发送命令'); + this.applySceneStatus(runtime, 'terminalInput', '调用 terminal_input'); const { sessionId } = await this.ensureTerminalSessionReady(payload, { focusPrompt: true, activate: true }); const command = payload?.arguments?.command || @@ -3892,10 +3920,11 @@ export class MonitorDirector implements MonitorDriver { return div; } - private openFolder(folderKey: string, label?: string) { + private async openFolder(folderKey: string, label?: string) { if (!folderKey) { return; } + await this.loadFolderEntries(folderKey); this.activeFolder = folderKey; this.showWindow(this.elements.folderWindow); this.elements.folderHeaderText.textContent = label || folderKey || 'workspace'; diff --git a/static/src/components/chat/monitor/progressMap.ts b/static/src/components/chat/monitor/progressMap.ts index 9689c5f..bfadac7 100644 --- a/static/src/components/chat/monitor/progressMap.ts +++ b/static/src/components/chat/monitor/progressMap.ts @@ -11,11 +11,11 @@ export const SCENE_PROGRESS_LABELS: Record = { focus: '正在聚焦', unfocus: '正在处理', // 运行类工具显示具体工具名,由运行时传入 - runCommand: '', - runPython: '', - terminalSession: '正在连接终端', - terminalInput: '', - terminalSnapshot: '正在获取终端', + runCommand: '运行命令', + runPython: '运行 Python', + terminalSession: '打开终端', + terminalInput: '终端输入', + terminalSnapshot: '获取终端输出', memoryUpdate: '正在同步记忆', todoCreate: '正在更新待办', todoUpdate: '正在更新待办', @@ -28,11 +28,11 @@ export const SCENE_PROGRESS_LABELS: Record = { renameFile: '正在重命名', terminalReset: '正在重置终端', terminalSleep: '准备等待', - terminalRun: '', + terminalRun: '终端运行中', ocr: '正在提取', memory: '正在同步记忆', todo: '正在管理待办', - genericTool: '' + genericTool: '调用工具' }; export function getSceneProgressLabel(name: string): string | null { diff --git a/static/src/utils/chatDisplay.ts b/static/src/utils/chatDisplay.ts index 7ee8f0d..dc6cee9 100644 --- a/static/src/utils/chatDisplay.ts +++ b/static/src/utils/chatDisplay.ts @@ -44,11 +44,11 @@ const RUNNING_STATUS_TEXTS: Record = { web_search: '正在搜索网络...', extract_webpage: '正在提取网页...', save_webpage: '正在保存网页...', - run_python: '', - run_command: '', + run_python: '调用 run_python', + run_command: '调用 run_command', update_memory: '正在更新记忆...', terminal_session: '正在管理终端会话...', - terminal_input: '', + terminal_input: '调用 terminal_input', terminal_snapshot: '正在获取终端快照...', terminal_reset: '正在重置终端...' }; @@ -178,7 +178,7 @@ export function getToolStatusText(tool: any): string { tool.display_name || tool.name || ''; - return label ? `调用 ${label}` : '调用工具中'; + return label ? label : '调用工具中'; } if (tool.status === 'completed') { if (tool.name === 'read_file') {