fix(ui): load folder contents in monitor and improve messages

This commit is contained in:
JOJO 2025-12-15 22:34:25 +08:00
parent 3729cae4d2
commit c96e99cd13
4 changed files with 67 additions and 38 deletions

View File

@ -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", {

View File

@ -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';

View File

@ -11,11 +11,11 @@ export const SCENE_PROGRESS_LABELS: Record<string, string> = {
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<string, string> = {
renameFile: '正在重命名',
terminalReset: '正在重置终端',
terminalSleep: '准备等待',
terminalRun: '',
terminalRun: '终端运行中',
ocr: '正在提取',
memory: '正在同步记忆',
todo: '正在管理待办',
genericTool: ''
genericTool: '调用工具'
};
export function getSceneProgressLabel(name: string): string | null {

View File

@ -44,11 +44,11 @@ const RUNNING_STATUS_TEXTS: Record<string, string> = {
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') {