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,
|
||
},
|
||
],
|
||
}
|
||
}
|