131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
|
|
import type { Dispatch, SetStateAction } from 'react'
|
|||
|
|
import type { AiAgentMessage } from './aiAgentConversation'
|
|||
|
|
import type { AppLocale } from './i18n'
|
|||
|
|
|
|||
|
|
export interface ToolInvocation {
|
|||
|
|
tool: string
|
|||
|
|
input?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function updateMessage(
|
|||
|
|
setMessages: Dispatch<SetStateAction<AiAgentMessage[]>>,
|
|||
|
|
messageId: string,
|
|||
|
|
updater: (message: AiAgentMessage) => AiAgentMessage,
|
|||
|
|
): void {
|
|||
|
|
setMessages((current) => current.map((message) => (message.id === messageId ? updater(message) : message)))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function markReasoningDone(
|
|||
|
|
setMessages: Dispatch<SetStateAction<AiAgentMessage[]>>,
|
|||
|
|
messageId: string,
|
|||
|
|
): void {
|
|||
|
|
updateMessage(setMessages, messageId, (message) => (
|
|||
|
|
message.reasoningDone ? message : { ...message, reasoningDone: true }
|
|||
|
|
))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type ToolActionStatus = 'pending' | 'done' | 'error'
|
|||
|
|
|
|||
|
|
function parsedToolInput(input?: string): Record<string, unknown> {
|
|||
|
|
if (!input) return {}
|
|||
|
|
try {
|
|||
|
|
const parsed: unknown = JSON.parse(input)
|
|||
|
|
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|||
|
|
? parsed as Record<string, unknown>
|
|||
|
|
: {}
|
|||
|
|
} catch {
|
|||
|
|
return {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function toolDetail(input?: string): string | undefined {
|
|||
|
|
const parsed = parsedToolInput(input)
|
|||
|
|
for (const key of ['purpose', 'query', 'url', 'path', 'file_path']) {
|
|||
|
|
const value = parsed[key]
|
|||
|
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
|||
|
|
}
|
|||
|
|
return undefined
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function chineseToolVerb(toolName: string, status: ToolActionStatus): string {
|
|||
|
|
const verbs: Record<string, [string, string, string]> = {
|
|||
|
|
magic_brush: ['正在执行', '已完成', '执行失败'],
|
|||
|
|
search_web: ['正在联网搜索', '已联网搜索', '联网搜索失败'],
|
|||
|
|
read_web_page: ['正在浏览网页', '已浏览网页', '网页浏览失败'],
|
|||
|
|
read_guanghu_url: ['正在读取光湖页面', '已读取光湖页面', '光湖页面读取失败'],
|
|||
|
|
search_notes: ['正在搜索知识库', '已搜索知识库', '知识库搜索失败'],
|
|||
|
|
get_vault_context: ['正在了解知识库', '已读取知识库概况', '知识库概况读取失败'],
|
|||
|
|
get_note: ['正在读取页面', '已读取页面', '页面读取失败'],
|
|||
|
|
open_note: ['正在打开页面', '已打开页面', '页面打开失败'],
|
|||
|
|
create_note: ['正在新建页面', '已新建页面', '页面新建失败'],
|
|||
|
|
edit_note: ['正在写入页面', '已写入页面', '页面写入失败'],
|
|||
|
|
delete_note: ['正在删除页面', '已删除页面', '页面删除失败'],
|
|||
|
|
get_fifth_domain_wake_route: ['正在读取第五域路径', '已读取第五域路径', '第五域路径读取失败'],
|
|||
|
|
get_current_time: ['正在校准当前时间', '已校准当前时间', '当前时间校准失败'],
|
|||
|
|
Bash: ['正在运行命令', '已运行命令', '命令运行失败'],
|
|||
|
|
Write: ['正在写入文件', '已写入文件', '文件写入失败'],
|
|||
|
|
Edit: ['正在编辑文件', '已编辑文件', '文件编辑失败'],
|
|||
|
|
Read: ['正在读取文件', '已读取文件', '文件读取失败'],
|
|||
|
|
Glob: ['正在查找文件', '已查找文件', '文件查找失败'],
|
|||
|
|
Grep: ['正在搜索内容', '已搜索内容', '内容搜索失败'],
|
|||
|
|
}
|
|||
|
|
const index = status === 'pending' ? 0 : status === 'done' ? 1 : 2
|
|||
|
|
return verbs[toolName]?.[index] ?? (status === 'pending' ? `正在调用 ${toolName}` : status === 'done' ? `已调用 ${toolName}` : `${toolName} 调用失败`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function formatToolLabel(
|
|||
|
|
toolName: string,
|
|||
|
|
input?: string,
|
|||
|
|
locale: AppLocale = 'en',
|
|||
|
|
status: ToolActionStatus = 'pending',
|
|||
|
|
): string {
|
|||
|
|
if (locale.startsWith('zh')) {
|
|||
|
|
const detail = toolDetail(input)
|
|||
|
|
const verb = chineseToolVerb(toolName, status)
|
|||
|
|
return detail ? `${verb}:${detail}` : verb
|
|||
|
|
}
|
|||
|
|
if (toolName === 'Bash') {
|
|||
|
|
return 'Ran shell command'
|
|||
|
|
}
|
|||
|
|
if (toolName === 'Write') return 'Wrote file'
|
|||
|
|
if (toolName === 'Edit') return 'Edited file'
|
|||
|
|
return toolName
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function updateToolAction(
|
|||
|
|
message: AiAgentMessage,
|
|||
|
|
toolName: string,
|
|||
|
|
toolId: string,
|
|||
|
|
input?: string,
|
|||
|
|
locale: AppLocale = 'en',
|
|||
|
|
): AiAgentMessage {
|
|||
|
|
const existing = message.actions.find((action) => action.toolId === toolId)
|
|||
|
|
if (existing) {
|
|||
|
|
return {
|
|||
|
|
...message,
|
|||
|
|
actions: message.actions.map((action) => (
|
|||
|
|
action.toolId === toolId
|
|||
|
|
? {
|
|||
|
|
...action,
|
|||
|
|
input: input ?? action.input,
|
|||
|
|
label: formatToolLabel(toolName, input ?? action.input, locale),
|
|||
|
|
}
|
|||
|
|
: action
|
|||
|
|
)),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
...message,
|
|||
|
|
actions: [
|
|||
|
|
...message.actions,
|
|||
|
|
{
|
|||
|
|
tool: toolName,
|
|||
|
|
toolId,
|
|||
|
|
label: formatToolLabel(toolName, input, locale),
|
|||
|
|
status: 'pending' as const,
|
|||
|
|
input,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
}
|