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:
|
if full_path.parent == self.project_path:
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": "禁止在项目根目录直接创建文件,请先创建或选择子目录。",
|
"error": "禁止在项目根目录直接创建文件,请先创建或选择子目录(例如 ./data 或 ./docs)。",
|
||||||
"suggestion": "创建文件所属文件夹,在其中创建新文件。"
|
"suggestion": "请先创建文件夹,再在该文件夹中创建新文件。"
|
||||||
}
|
}
|
||||||
if self._use_container():
|
if self._use_container():
|
||||||
result = self._container_call("create_file", {
|
result = self._container_call("create_file", {
|
||||||
|
|||||||
@ -96,7 +96,7 @@ type EditorOperation = {
|
|||||||
const EDITOR_MAX_RENDER_LINES = 360;
|
const EDITOR_MAX_RENDER_LINES = 360;
|
||||||
const EDITOR_DIFF_LIMIT = 2000;
|
const EDITOR_DIFF_LIMIT = 2000;
|
||||||
const EDITOR_MAX_ANIMATION_STEPS = 4000;
|
const EDITOR_MAX_ANIMATION_STEPS = 4000;
|
||||||
const EDITOR_TYPING_THRESHOLD = 4096;
|
const EDITOR_TYPING_THRESHOLD = 180;
|
||||||
const EDITOR_TYPING_INTERVAL = 34;
|
const EDITOR_TYPING_INTERVAL = 34;
|
||||||
const EDITOR_ERASE_INTERVAL = 26;
|
const EDITOR_ERASE_INTERVAL = 26;
|
||||||
const MONITOR_EDITOR_DEBUG = false;
|
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) {
|
private renderFolderEntries(folderKey: string, animate = true) {
|
||||||
this.ensureFolderKey(folderKey);
|
this.ensureFolderKey(folderKey);
|
||||||
const entries = this.folderEntries.get(folderKey) || [];
|
const entries = this.folderEntries.get(folderKey) || [];
|
||||||
@ -840,7 +864,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
await this.movePointerToElement(icon, { duration: 720 });
|
await this.movePointerToElement(icon, { duration: 720 });
|
||||||
await this.click({ count: 2 });
|
await this.click({ count: 2 });
|
||||||
await sleep(180);
|
await sleep(180);
|
||||||
this.openFolder(folderName, folderName);
|
await this.openFolder(folderName, folderName);
|
||||||
await sleep(60);
|
await sleep(60);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,7 +893,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.upsertFolderEntry(parentKey, { name: filename, type: 'file' }, { animate: false });
|
this.upsertFolderEntry(parentKey, { name: filename, type: 'file' }, { animate: false });
|
||||||
this.openFolder(parentKey, parentKey);
|
await this.openFolder(parentKey, parentKey);
|
||||||
await sleep(40);
|
await sleep(40);
|
||||||
const entryPath = this.composePath([parentKey, filename]);
|
const entryPath = this.composePath([parentKey, filename]);
|
||||||
const entryEl = this.findFolderEntryElement(entryPath);
|
const entryEl = this.findFolderEntryElement(entryPath);
|
||||||
@ -935,7 +959,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
if (matches) {
|
if (matches) {
|
||||||
currentKey = this.composePath(activeSegments);
|
currentKey = this.composePath(activeSegments);
|
||||||
startIndex = activeSegments.length;
|
startIndex = activeSegments.length;
|
||||||
this.openFolder(currentKey, currentKey);
|
await this.openFolder(currentKey, currentKey);
|
||||||
await sleep(40);
|
await sleep(40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -952,11 +976,11 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
const segment = targetSegments[i];
|
const segment = targetSegments[i];
|
||||||
const nextKey = this.composePath([currentKey, segment]);
|
const nextKey = this.composePath([currentKey, segment]);
|
||||||
this.upsertFolderEntry(currentKey, { name: segment, type: 'folder' }, { animate: false });
|
this.upsertFolderEntry(currentKey, { name: segment, type: 'folder' }, { animate: false });
|
||||||
this.openFolder(currentKey, currentKey);
|
await this.openFolder(currentKey, currentKey);
|
||||||
await sleep(40);
|
await sleep(40);
|
||||||
await this.doubleClickFolderEntry(nextKey);
|
await this.doubleClickFolderEntry(nextKey);
|
||||||
currentKey = nextKey;
|
currentKey = nextKey;
|
||||||
this.openFolder(currentKey, currentKey);
|
await this.openFolder(currentKey, currentKey);
|
||||||
await sleep(60);
|
await sleep(60);
|
||||||
}
|
}
|
||||||
return currentKey;
|
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 lines = text.split('\n');
|
||||||
const long = lines.length > 8 || text.length > 400;
|
const long = forceLineMode || lines.length > 2 || text.length > 120;
|
||||||
const lineDelay = 55;
|
const lineDelay = 32;
|
||||||
if (long) {
|
if (long) {
|
||||||
target.textContent = '';
|
target.textContent = '';
|
||||||
lines.forEach((line, idx) => {
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
target.textContent += (idx > 0 ? '\n' : '') + line;
|
target.textContent += (i > 0 ? '\n' : '') + lines[i];
|
||||||
});
|
await sleep(Math.max(12, lineDelay - Math.min(i, 6) * 3));
|
||||||
// 行级动画:逐行显现
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const chars = Array.from(text);
|
const chars = Array.from(text);
|
||||||
for (const ch of chars) {
|
for (const ch of chars) {
|
||||||
target.textContent = (target.textContent || '') + ch;
|
target.textContent = (target.textContent || '') + ch;
|
||||||
await sleep(26);
|
await sleep(18);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1781,6 +1799,16 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
this.renderTerminalHistory(sessionId);
|
this.renderTerminalHistory(sessionId);
|
||||||
const { bodyEl } = this.getTerminalInstance();
|
const { bodyEl } = this.getTerminalInstance();
|
||||||
const promptEl = bodyEl.lastElementChild as HTMLElement | null;
|
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;
|
let charIndex = 0;
|
||||||
const chars = command.split('');
|
const chars = command.split('');
|
||||||
for (const ch of chars) {
|
for (const ch of chars) {
|
||||||
@ -1792,7 +1820,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
if (promptEl && (charIndex % 6 === 0 || charIndex === chars.length)) {
|
if (promptEl && (charIndex % 6 === 0 || charIndex === chars.length)) {
|
||||||
this.scrollPromptIntoView(this.getTerminalInstance());
|
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 existing = (this.folderEntries.get(parentKey) || []).find(item => item.name === fromName);
|
||||||
const entryType = existing?.type || 'file';
|
const entryType = existing?.type || 'file';
|
||||||
this.upsertFolderEntry(parentKey, { name: fromName, type: entryType }, { animate: false });
|
this.upsertFolderEntry(parentKey, { name: fromName, type: entryType }, { animate: false });
|
||||||
this.openFolder(parentKey, parentKey);
|
await this.openFolder(parentKey, parentKey);
|
||||||
await sleep(40);
|
await sleep(40);
|
||||||
const entryPath = this.composePath([parentKey, fromName]);
|
const entryPath = this.composePath([parentKey, fromName]);
|
||||||
const entryEl = this.findFolderEntryElement(entryPath);
|
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 existing = (this.folderEntries.get(parentKey) || []).find(item => item.name === name);
|
||||||
const entryType = existing?.type || 'file';
|
const entryType = existing?.type || 'file';
|
||||||
this.upsertFolderEntry(parentKey, { name, type: entryType }, { animate: false });
|
this.upsertFolderEntry(parentKey, { name, type: entryType }, { animate: false });
|
||||||
this.openFolder(parentKey, parentKey);
|
await this.openFolder(parentKey, parentKey);
|
||||||
await sleep(40);
|
await sleep(40);
|
||||||
const entryPath = this.composePath([parentKey, name]);
|
const entryPath = this.composePath([parentKey, name]);
|
||||||
const entryEl = this.findFolderEntryElement(entryPath);
|
const entryEl = this.findFolderEntryElement(entryPath);
|
||||||
@ -2899,7 +2927,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.sceneHandlers.terminalSession = async (payload, runtime) => {
|
this.sceneHandlers.terminalSession = async (payload, runtime) => {
|
||||||
this.applySceneStatus(runtime, 'terminalSession', '正在查看终端');
|
this.applySceneStatus(runtime, 'terminalSession', '打开终端');
|
||||||
const action = (payload?.arguments?.action || payload?.action || '').toLowerCase();
|
const action = (payload?.arguments?.action || payload?.action || '').toLowerCase();
|
||||||
// 特殊处理:如果是关闭/切换终端,不要无意中新建会话
|
// 特殊处理:如果是关闭/切换终端,不要无意中新建会话
|
||||||
if (action === 'close' || action === 'switch') {
|
if (action === 'close' || action === 'switch') {
|
||||||
@ -2969,7 +2997,7 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.sceneHandlers.terminalInput = async (payload, runtime) => {
|
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 { sessionId } = await this.ensureTerminalSessionReady(payload, { focusPrompt: true, activate: true });
|
||||||
const command =
|
const command =
|
||||||
payload?.arguments?.command ||
|
payload?.arguments?.command ||
|
||||||
@ -3892,10 +3920,11 @@ export class MonitorDirector implements MonitorDriver {
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
private openFolder(folderKey: string, label?: string) {
|
private async openFolder(folderKey: string, label?: string) {
|
||||||
if (!folderKey) {
|
if (!folderKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.loadFolderEntries(folderKey);
|
||||||
this.activeFolder = folderKey;
|
this.activeFolder = folderKey;
|
||||||
this.showWindow(this.elements.folderWindow);
|
this.showWindow(this.elements.folderWindow);
|
||||||
this.elements.folderHeaderText.textContent = label || folderKey || 'workspace';
|
this.elements.folderHeaderText.textContent = label || folderKey || 'workspace';
|
||||||
|
|||||||
@ -11,11 +11,11 @@ export const SCENE_PROGRESS_LABELS: Record<string, string> = {
|
|||||||
focus: '正在聚焦',
|
focus: '正在聚焦',
|
||||||
unfocus: '正在处理',
|
unfocus: '正在处理',
|
||||||
// 运行类工具显示具体工具名,由运行时传入
|
// 运行类工具显示具体工具名,由运行时传入
|
||||||
runCommand: '',
|
runCommand: '运行命令',
|
||||||
runPython: '',
|
runPython: '运行 Python',
|
||||||
terminalSession: '正在连接终端',
|
terminalSession: '打开终端',
|
||||||
terminalInput: '',
|
terminalInput: '终端输入',
|
||||||
terminalSnapshot: '正在获取终端',
|
terminalSnapshot: '获取终端输出',
|
||||||
memoryUpdate: '正在同步记忆',
|
memoryUpdate: '正在同步记忆',
|
||||||
todoCreate: '正在更新待办',
|
todoCreate: '正在更新待办',
|
||||||
todoUpdate: '正在更新待办',
|
todoUpdate: '正在更新待办',
|
||||||
@ -28,11 +28,11 @@ export const SCENE_PROGRESS_LABELS: Record<string, string> = {
|
|||||||
renameFile: '正在重命名',
|
renameFile: '正在重命名',
|
||||||
terminalReset: '正在重置终端',
|
terminalReset: '正在重置终端',
|
||||||
terminalSleep: '准备等待',
|
terminalSleep: '准备等待',
|
||||||
terminalRun: '',
|
terminalRun: '终端运行中',
|
||||||
ocr: '正在提取',
|
ocr: '正在提取',
|
||||||
memory: '正在同步记忆',
|
memory: '正在同步记忆',
|
||||||
todo: '正在管理待办',
|
todo: '正在管理待办',
|
||||||
genericTool: ''
|
genericTool: '调用工具'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getSceneProgressLabel(name: string): string | null {
|
export function getSceneProgressLabel(name: string): string | null {
|
||||||
|
|||||||
@ -44,11 +44,11 @@ const RUNNING_STATUS_TEXTS: Record<string, string> = {
|
|||||||
web_search: '正在搜索网络...',
|
web_search: '正在搜索网络...',
|
||||||
extract_webpage: '正在提取网页...',
|
extract_webpage: '正在提取网页...',
|
||||||
save_webpage: '正在保存网页...',
|
save_webpage: '正在保存网页...',
|
||||||
run_python: '',
|
run_python: '调用 run_python',
|
||||||
run_command: '',
|
run_command: '调用 run_command',
|
||||||
update_memory: '正在更新记忆...',
|
update_memory: '正在更新记忆...',
|
||||||
terminal_session: '正在管理终端会话...',
|
terminal_session: '正在管理终端会话...',
|
||||||
terminal_input: '',
|
terminal_input: '调用 terminal_input',
|
||||||
terminal_snapshot: '正在获取终端快照...',
|
terminal_snapshot: '正在获取终端快照...',
|
||||||
terminal_reset: '正在重置终端...'
|
terminal_reset: '正在重置终端...'
|
||||||
};
|
};
|
||||||
@ -178,7 +178,7 @@ export function getToolStatusText(tool: any): string {
|
|||||||
tool.display_name ||
|
tool.display_name ||
|
||||||
tool.name ||
|
tool.name ||
|
||||||
'';
|
'';
|
||||||
return label ? `调用 ${label}` : '调用工具中';
|
return label ? label : '调用工具中';
|
||||||
}
|
}
|
||||||
if (tool.status === 'completed') {
|
if (tool.status === 'completed') {
|
||||||
if (tool.name === 'read_file') {
|
if (tool.name === 'read_file') {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user