fix(ui): load folder contents in monitor and improve messages
This commit is contained in:
parent
3729cae4d2
commit
c96e99cd13
@ -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", {
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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') {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user