276 lines
8.1 KiB
TypeScript
276 lines
8.1 KiB
TypeScript
import { TOOL_ICON_MAP } from './icons';
|
|
|
|
type ToolPayload = Record<string, any> | null | undefined;
|
|
|
|
const RUNNING_ANIMATIONS: Record<string, string> = {
|
|
create_file: 'file-animation',
|
|
read_file: 'read-animation',
|
|
delete_file: 'file-animation',
|
|
rename_file: 'file-animation',
|
|
modify_file: 'file-animation',
|
|
append_to_file: 'file-animation',
|
|
create_folder: 'file-animation',
|
|
focus_file: 'focus-animation',
|
|
unfocus_file: 'focus-animation',
|
|
web_search: 'search-animation',
|
|
extract_webpage: 'search-animation',
|
|
save_webpage: 'file-animation',
|
|
run_python: 'code-animation',
|
|
run_command: 'terminal-animation',
|
|
update_memory: 'memory-animation',
|
|
sleep: 'wait-animation',
|
|
terminal_session: 'terminal-animation',
|
|
terminal_input: 'terminal-animation',
|
|
terminal_snapshot: 'terminal-animation',
|
|
terminal_reset: 'terminal-animation',
|
|
todo_create: 'file-animation',
|
|
todo_update_task: 'file-animation',
|
|
todo_finish: 'file-animation',
|
|
todo_finish_confirm: 'file-animation',
|
|
create_sub_agent: 'terminal-animation',
|
|
wait_sub_agent: 'wait-animation'
|
|
};
|
|
|
|
const RUNNING_STATUS_TEXTS: Record<string, string> = {
|
|
create_file: '正在创建文件...',
|
|
sleep: '正在等待...',
|
|
delete_file: '正在删除文件...',
|
|
rename_file: '正在重命名文件...',
|
|
modify_file: '正在修改文件...',
|
|
append_to_file: '正在追加文件...',
|
|
create_folder: '正在创建文件夹...',
|
|
focus_file: '正在聚焦文件...',
|
|
unfocus_file: '正在取消聚焦...',
|
|
web_search: '正在搜索网络...',
|
|
extract_webpage: '正在提取网页...',
|
|
save_webpage: '正在保存网页...',
|
|
run_python: '调用 run_python',
|
|
run_command: '调用 run_command',
|
|
update_memory: '正在更新记忆...',
|
|
terminal_session: '正在管理终端会话...',
|
|
terminal_input: '调用 terminal_input',
|
|
terminal_snapshot: '正在获取终端快照...',
|
|
terminal_reset: '正在重置终端...'
|
|
};
|
|
|
|
const COMPLETED_STATUS_TEXTS: Record<string, string> = {
|
|
create_file: '文件创建成功',
|
|
delete_file: '文件删除成功',
|
|
sleep: '等待完成',
|
|
rename_file: '文件重命名成功',
|
|
modify_file: '文件修改成功',
|
|
append_to_file: '文件追加完成',
|
|
create_folder: '文件夹创建成功',
|
|
focus_file: '文件聚焦成功',
|
|
unfocus_file: '取消聚焦成功',
|
|
web_search: '搜索完成',
|
|
extract_webpage: '网页提取完成',
|
|
save_webpage: '网页保存完成(纯文本)',
|
|
run_python: '代码执行完成',
|
|
run_command: '命令执行完成',
|
|
update_memory: '记忆更新成功',
|
|
terminal_session: '终端操作完成',
|
|
terminal_input: '终端输入完成',
|
|
terminal_snapshot: '终端快照已返回',
|
|
terminal_reset: '终端已重置'
|
|
};
|
|
|
|
const LANGUAGE_CLASS_MAP: Record<string, string> = {
|
|
py: 'language-python',
|
|
js: 'language-javascript',
|
|
html: 'language-html',
|
|
css: 'language-css',
|
|
json: 'language-json',
|
|
md: 'language-markdown',
|
|
txt: 'language-plain'
|
|
};
|
|
|
|
const SEARCH_TOPIC_MAP: Record<string, string> = {
|
|
general: '通用',
|
|
news: '新闻',
|
|
finance: '金融'
|
|
};
|
|
|
|
const RELATIVE_TIME_RANGE_MAP: Record<string, string> = {
|
|
day: '过去24小时',
|
|
week: '过去7天',
|
|
month: '过去30天',
|
|
year: '过去365天'
|
|
};
|
|
|
|
export function getToolIcon(tool: any): string {
|
|
const toolName = typeof tool === 'string' ? tool : tool?.name;
|
|
return TOOL_ICON_MAP[toolName as keyof typeof TOOL_ICON_MAP] || 'settings';
|
|
}
|
|
|
|
export function getToolAnimationClass(tool: any): string {
|
|
if (!tool) {
|
|
return '';
|
|
}
|
|
if (tool.status === 'hinted') {
|
|
return 'hint-animation pulse-slow';
|
|
}
|
|
if (tool.status === 'preparing') {
|
|
return 'preparing-animation';
|
|
}
|
|
if (tool.status === 'running') {
|
|
return RUNNING_ANIMATIONS[tool.name] || 'default-animation';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function describeReadFileResult(tool: any): string {
|
|
if (!tool?.result || typeof tool.result !== 'object') {
|
|
return '文件读取完成';
|
|
}
|
|
const readType = String(tool.result.type || 'read').toLowerCase();
|
|
if (readType === 'search') {
|
|
const query = tool.result.query ? `「${tool.result.query}」` : '';
|
|
const count =
|
|
typeof tool.result.returned_matches === 'number'
|
|
? tool.result.returned_matches
|
|
: tool.result.actual_matches || 0;
|
|
return `搜索${query},得到${count}个结果`;
|
|
}
|
|
if (readType === 'extract') {
|
|
const segments = Array.isArray(tool.result.segments) ? tool.result.segments : [];
|
|
const totalLines = segments.reduce((sum: number, seg: any) => {
|
|
const start = Number(seg.line_start) || 0;
|
|
const end = Number(seg.line_end) || 0;
|
|
if (!start || !end || end < start) {
|
|
return sum;
|
|
}
|
|
return sum + (end - start + 1);
|
|
}, 0);
|
|
const displayLines = totalLines || tool.result.char_count || 0;
|
|
return `提取了${displayLines}行`;
|
|
}
|
|
return '文件读取完成';
|
|
}
|
|
|
|
export function getToolStatusText(tool: any): string {
|
|
if (!tool) {
|
|
return '';
|
|
}
|
|
if (tool.message) {
|
|
return tool.message;
|
|
}
|
|
if (tool.status === 'hinted') {
|
|
return `可能需要 ${tool.name}...`;
|
|
}
|
|
if (tool.status === 'preparing') {
|
|
return `准备调用 ${tool.name}...`;
|
|
}
|
|
if (tool.status === 'running') {
|
|
if (tool.name === 'read_file') {
|
|
const readType = String(
|
|
tool.argumentSnapshot?.type || tool.arguments?.type || 'read'
|
|
).toLowerCase();
|
|
const runningMap: Record<string, string> = {
|
|
read: '正在读取文件...',
|
|
search: '正在执行搜索...',
|
|
extract: '正在提取内容...'
|
|
};
|
|
return runningMap[readType] || '正在读取文件...';
|
|
}
|
|
const label =
|
|
RUNNING_STATUS_TEXTS[tool.name] ||
|
|
tool.display_name ||
|
|
tool.name ||
|
|
'';
|
|
return label ? label : '调用工具中';
|
|
}
|
|
if (tool.status === 'completed') {
|
|
if (tool.name === 'read_file') {
|
|
return describeReadFileResult(tool);
|
|
}
|
|
return COMPLETED_STATUS_TEXTS[tool.name] || '执行完成';
|
|
}
|
|
return `${tool.name} - ${tool.status}`;
|
|
}
|
|
|
|
export function getToolDescription(tool: any): string {
|
|
if (!tool) {
|
|
return '';
|
|
}
|
|
const args = tool.argumentSnapshot || tool.arguments;
|
|
const argumentLabel = tool.argumentLabel || buildToolLabel(args);
|
|
if (argumentLabel) {
|
|
return argumentLabel;
|
|
}
|
|
if (tool.statusDetail) {
|
|
return tool.statusDetail;
|
|
}
|
|
if (tool.result && typeof tool.result === 'object' && tool.result.path) {
|
|
return String(tool.result.path).split('/').pop() || '';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
export function cloneToolArguments(args: any): any {
|
|
if (!args || typeof args !== 'object') {
|
|
return null;
|
|
}
|
|
try {
|
|
return JSON.parse(JSON.stringify(args));
|
|
} catch (error) {
|
|
console.warn('无法克隆工具参数:', error);
|
|
return { ...args };
|
|
}
|
|
}
|
|
|
|
export function buildToolLabel(args: any): string {
|
|
if (!args || typeof args !== 'object') {
|
|
return '';
|
|
}
|
|
if (args.command) {
|
|
return args.command;
|
|
}
|
|
if (args.path) {
|
|
return String(args.path).split('/').pop() || '';
|
|
}
|
|
if (args.target_path) {
|
|
return String(args.target_path).split('/').pop() || '';
|
|
}
|
|
if (args.query) {
|
|
return `"${args.query}"`;
|
|
}
|
|
if (typeof args.seconds !== 'undefined') {
|
|
return `${args.seconds} 秒`;
|
|
}
|
|
if (args.name) {
|
|
return args.name;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
export function formatSearchTopic(filters: ToolPayload): string {
|
|
const topic = filters?.topic ? String(filters.topic).toLowerCase() : 'general';
|
|
return SEARCH_TOPIC_MAP[topic] || '通用';
|
|
}
|
|
|
|
export function formatSearchTime(filters: ToolPayload): string {
|
|
if (!filters) {
|
|
return '未限定时间';
|
|
}
|
|
if (filters.time_range) {
|
|
const key = String(filters.time_range).toLowerCase();
|
|
return RELATIVE_TIME_RANGE_MAP[key] || `相对范围:${filters.time_range}`;
|
|
}
|
|
if (typeof filters.days === 'number') {
|
|
return `过去${filters.days}天`;
|
|
}
|
|
if (filters.start_date && filters.end_date) {
|
|
return `${filters.start_date} 至 ${filters.end_date}`;
|
|
}
|
|
return '未限定时间';
|
|
}
|
|
|
|
export function getLanguageClass(path: string): string {
|
|
if (!path) {
|
|
return 'language-plain';
|
|
}
|
|
const ext = path.split('.').pop()?.toLowerCase() || '';
|
|
return LANGUAGE_CLASS_MAP[ext] || 'language-plain';
|
|
}
|