feat(frontend): 完成智能体页面汉化
This commit is contained in:
@@ -20,7 +20,7 @@ const commands = computed<Command[]>(() => [
|
||||
{ id: 'workspace', label: '打开工作区', hint: '导航', run: () => router.push('/workspace') },
|
||||
{ id: 'search', label: '全局搜索', hint: '导航', run: () => router.push('/search') },
|
||||
{ id: 'chat', label: '打开 AI 对话', hint: '导航', run: () => router.push('/chat') },
|
||||
{ id: 'agent', label: '创建 Agent Run', hint: '导航', run: () => router.push('/agent/runs') },
|
||||
{ id: 'agent', label: '创建智能体运行', hint: '导航', run: () => router.push('/agent/runs') },
|
||||
{ id: 'settings', label: '打开设置', hint: '导航', run: () => router.push('/settings') },
|
||||
{ id: 'mode', label: `切换为${editorStore.mode === 'source' ? '写作' : '源码'}模式`, hint: '编辑器', run: () => editorStore.toggleMode() },
|
||||
{ id: 'save', label: '保存当前笔记', hint: '编辑器', run: () => editorStore.save() },
|
||||
|
||||
@@ -12,7 +12,7 @@ const navItems = [
|
||||
{ name: 'workspace', icon: FolderOpened, label: '工作区' },
|
||||
{ name: 'search', icon: Search, label: '搜索' },
|
||||
{ name: 'chat', icon: ChatDotRound, label: 'AI 对话' },
|
||||
{ name: 'agent', icon: Cpu, label: 'Agent' },
|
||||
{ name: 'agent', icon: Cpu, label: '智能体' },
|
||||
{ name: 'tasks', icon: CircleCheck, label: '任务' },
|
||||
{ name: 'skills', icon: Lightning, label: 'Skill' },
|
||||
{ name: 'plugins', icon: Connection, label: 'Plugin' },
|
||||
|
||||
@@ -19,7 +19,7 @@ const sidebarTitle = computed(() => {
|
||||
const titles: Record<string, string> = {
|
||||
'file-tree': '文件',
|
||||
'conversation-list': '对话',
|
||||
'run-list': 'Agent Run',
|
||||
'run-list': '智能体运行',
|
||||
'search-filters': '搜索筛选',
|
||||
'task-filters': '任务筛选',
|
||||
'extension-list': '扩展',
|
||||
|
||||
@@ -84,7 +84,7 @@ const showEditorInfo = computed(() => route.name === 'workspace')
|
||||
</span>
|
||||
<span v-if="agentStore.isRunning" class="status-item agent-status">
|
||||
<span class="spinner" />
|
||||
Agent 运行中
|
||||
智能体运行中
|
||||
</span>
|
||||
</div>
|
||||
<div class="statusbar-right">
|
||||
|
||||
@@ -18,7 +18,7 @@ const pageTitle = computed(() => {
|
||||
workspace: '工作区',
|
||||
search: '搜索',
|
||||
chat: 'AI 对话',
|
||||
agent: 'Agent Trace',
|
||||
agent: '智能体执行轨迹',
|
||||
tasks: '任务',
|
||||
skills: 'Skill 管理',
|
||||
plugins: 'Plugin 管理',
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { useProviderStore } from '@/stores/provider'
|
||||
import { useSkillStore } from '@/stores/skill'
|
||||
import type { AgentEvent } from '@/contracts'
|
||||
import { eventLabel, localizeDetails, permissionLabel, runStatusLabel, toolDescription, toolLabel } from './labels'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -24,12 +26,12 @@ onMounted(async () => {
|
||||
try {
|
||||
await Promise.all([providerStore.loadProviders(), skillStore.loadSkills(), agentStore.loadTools()])
|
||||
await providerStore.loadModels(form.provider_id)
|
||||
} catch (error) { pageError.value = error instanceof Error ? error.message : 'Agent 配置加载失败' }
|
||||
} catch (error) { pageError.value = error instanceof Error ? error.message : '智能体配置加载失败' }
|
||||
})
|
||||
|
||||
watch(() => route.params.runId, async (runId) => {
|
||||
if (typeof runId !== 'string') return
|
||||
try { await agentStore.loadRun(runId) } catch (error) { pageError.value = error instanceof Error ? error.message : 'Run 加载失败' }
|
||||
try { await agentStore.loadRun(runId) } catch (error) { pageError.value = error instanceof Error ? error.message : '运行记录加载失败' }
|
||||
}, { immediate: true })
|
||||
|
||||
watch(() => form.provider_id, async (providerId) => {
|
||||
@@ -53,50 +55,54 @@ async function createRun() {
|
||||
allow_network: form.allow_network, max_concurrent_tools: form.max_concurrent_tools,
|
||||
})
|
||||
await router.replace({ name: 'agent', params: { runId: run.run_id } })
|
||||
} catch (error) { pageError.value = error instanceof Error ? error.message : 'Run 创建失败' }
|
||||
} catch (error) { pageError.value = error instanceof Error ? error.message : '运行创建失败' }
|
||||
}
|
||||
|
||||
function eventText(data: Record<string, unknown>) {
|
||||
return String(data.text ?? data.message ?? data.code ?? '')
|
||||
function eventText(event: AgentEvent) {
|
||||
if (event.event === 'RunCompleted') return '任务已成功完成。'
|
||||
if (event.event === 'RunCancelled') return '任务已取消。'
|
||||
const text = event.data.text ?? event.data.message ?? event.data.code
|
||||
if (text) return String(text)
|
||||
return ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="feature-page agent-page">
|
||||
<header class="feature-header"><div><h1>{{ isNewRun ? '创建 Agent Run' : 'Agent Trace' }}</h1><p>配置执行边界,并实时查看模型、工具和权限事件。</p></div>
|
||||
<button v-if="!isNewRun" class="button-secondary" @click="router.push({ name: 'agent' })">新建 Run</button></header>
|
||||
<header class="feature-header"><div><h1>{{ isNewRun ? '创建智能体运行' : '智能体执行轨迹' }}</h1><p>配置执行边界,并实时查看模型、工具和权限事件。</p></div>
|
||||
<button v-if="!isNewRun" class="button-secondary" @click="router.push({ name: 'agent' })">新建运行</button></header>
|
||||
<div v-if="pageError || agentStore.error" class="error-banner">{{ pageError || agentStore.error }}</div>
|
||||
<form v-if="isNewRun" class="panel run-form" @submit.prevent="createRun">
|
||||
<div class="field"><label>任务</label><textarea v-model="form.input" class="textarea" required placeholder="描述希望 Agent 完成的任务" /></div>
|
||||
<div class="field"><label>任务</label><textarea v-model="form.input" class="textarea" required placeholder="描述希望智能体完成的任务" /></div>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>Provider</label><select v-model="form.provider_id" class="select"><option v-for="p in providerStore.enabledProviders" :key="p.provider_id" :value="p.provider_id">{{ p.name }}</option></select></div>
|
||||
<div class="field"><label>Model</label><select v-model="form.model" class="select"><option v-for="m in models" :key="m.model_id" :value="m.model_id">{{ m.name }}</option></select></div>
|
||||
<div class="field"><label>Skill</label><select v-model="form.skill_id" class="select"><option value="">不使用 Skill</option><option v-for="s in skillStore.readySkills" :key="s.skill_id" :value="s.skill_id">{{ s.name }}</option></select></div>
|
||||
<div class="field"><label>模型提供商</label><select v-model="form.provider_id" class="select"><option v-for="p in providerStore.enabledProviders" :key="p.provider_id" :value="p.provider_id">{{ p.name }}</option></select></div>
|
||||
<div class="field"><label>模型</label><select v-model="form.model" class="select"><option v-for="m in models" :key="m.model_id" :value="m.model_id">{{ m.name }}</option></select></div>
|
||||
<div class="field"><label>技能</label><select v-model="form.skill_id" class="select"><option value="">不使用技能</option><option v-for="s in skillStore.readySkills" :key="s.skill_id" :value="s.skill_id">{{ s.name }}</option></select></div>
|
||||
<div class="field"><label>最大步骤</label><input v-model.number="form.max_steps" class="input" type="number" min="1" max="100" /></div>
|
||||
<div class="field"><label>Tool Timeout(秒)</label><input v-model.number="form.tool_timeout_seconds" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>Run Timeout(秒)</label><input v-model.number="form.run_timeout_seconds" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>Token Budget</label><input v-model.number="form.token_budget" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>工具超时(秒)</label><input v-model.number="form.tool_timeout_seconds" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>运行超时(秒)</label><input v-model.number="form.run_timeout_seconds" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>令牌预算</label><input v-model.number="form.token_budget" class="input" type="number" min="1" /></div>
|
||||
<div class="field"><label>最大并发工具</label><input v-model.number="form.max_concurrent_tools" class="input" type="number" min="1" /></div>
|
||||
</div>
|
||||
<div class="field"><label>允许的 Tool</label><div class="tool-grid"><label v-for="tool in agentStore.tools" :key="tool.name" class="tool-option"><input type="checkbox" :checked="form.allowed_tools.includes(tool.name)" @change="toggleTool(tool.name)" /><span><strong>{{ tool.name }}</strong><small>{{ tool.description }}</small></span></label></div></div>
|
||||
<label class="network"><input v-model="form.allow_network" type="checkbox" /> 允许本次 Run 调用网络工具</label>
|
||||
<div class="field"><label>允许使用的工具</label><div class="tool-grid"><label v-for="tool in agentStore.tools" :key="tool.name" class="tool-option"><input type="checkbox" :checked="form.allowed_tools.includes(tool.name)" @change="toggleTool(tool.name)" /><span><strong>{{ toolLabel(tool.name) }}</strong><code>{{ tool.name }}</code><small>{{ toolDescription(tool.name, tool.description) }}</small></span></label></div></div>
|
||||
<label class="network"><input v-model="form.allow_network" type="checkbox" /> 允许本次运行调用网络工具</label>
|
||||
<div class="inline-actions"><button class="button-primary" :disabled="agentStore.isCreating || !form.input.trim()">{{ agentStore.isCreating ? '创建中…' : '创建并运行' }}</button></div>
|
||||
</form>
|
||||
|
||||
<div v-else class="trace-layout">
|
||||
<div class="panel run-summary"><div><span class="badge info">{{ agentStore.activeRun?.status }}</span><h2>{{ agentStore.activeRunId }}</h2></div><div class="inline-actions"><span>步骤 {{ agentStore.currentStep }} / {{ agentStore.activeRun?.max_steps }}</span><button v-if="agentStore.isRunning" class="button-danger" @click="agentStore.cancelRun(agentStore.activeRunId!)">取消运行</button></div></div>
|
||||
<div class="panel run-summary"><div><span class="badge info">{{ runStatusLabel(agentStore.activeRun?.status) }}</span><h2>{{ agentStore.activeRunId }}</h2></div><div class="inline-actions"><span>步骤 {{ agentStore.currentStep }} / {{ agentStore.activeRun?.max_steps }}</span><button v-if="agentStore.isRunning" class="button-danger" @click="agentStore.cancelRun(agentStore.activeRunId!)">取消运行</button></div></div>
|
||||
<div class="timeline">
|
||||
<article v-for="event in agentStore.events" :key="event.sequence" class="event-card item-card">
|
||||
<div class="event-head"><span class="badge" :class="{ success: event.event === 'RunCompleted', error: event.event === 'RunFailed', warning: event.event === 'PermissionRequired' }">{{ event.event }}</span><span>#{{ event.sequence }} · {{ new Date(event.timestamp).toLocaleTimeString() }}</span></div>
|
||||
<p v-if="eventText(event.data)" class="event-text">{{ eventText(event.data) }}</p>
|
||||
<pre v-if="['ToolCall', 'ToolResult', 'Citation'].includes(event.event)">{{ JSON.stringify(event.data, null, 2) }}</pre>
|
||||
<div class="event-head"><span class="badge" :class="{ success: event.event === 'RunCompleted', error: event.event === 'RunFailed', warning: event.event === 'PermissionRequired' }">{{ eventLabel(event.event) }}</span><span>第 {{ event.sequence }} 条 · {{ new Date(event.timestamp).toLocaleTimeString() }}</span></div>
|
||||
<p v-if="eventText(event)" class="event-text">{{ eventText(event) }}</p>
|
||||
<pre v-if="['ToolCall', 'ToolResult', 'Citation', 'Usage'].includes(event.event)">{{ JSON.stringify(localizeDetails(event.data), null, 2) }}</pre>
|
||||
</article>
|
||||
<div v-if="!agentStore.events.length" class="empty-state"><div><strong>等待 Trace</strong><p>事件连接建立后将在这里实时显示。</p></div></div>
|
||||
<div v-if="!agentStore.events.length" class="empty-state"><div><strong>等待执行轨迹</strong><p>事件连接建立后将在这里实时显示。</p></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="agentStore.permissionRequest" class="modal-backdrop">
|
||||
<div class="modal"><span class="badge warning">权限确认</span><h2>{{ agentStore.permissionRequest.tool_name }}</h2><p>{{ agentStore.permissionRequest.impact }}</p><p class="subtle">权限:{{ agentStore.permissionRequest.permission }}</p><pre>{{ JSON.stringify(agentStore.permissionRequest.parameters, null, 2) }}</pre><div class="inline-actions permission-actions"><button class="button-primary" @click="agentStore.respondPermission('allow', 'once')">仅本次允许</button><button class="button-secondary" @click="agentStore.respondPermission('allow', 'session')">本次会话允许</button><button class="button-danger" @click="agentStore.respondPermission('deny')">拒绝</button></div></div>
|
||||
<div class="modal"><span class="badge warning">权限确认</span><h2>{{ toolLabel(agentStore.permissionRequest.tool_name) }}</h2><p>{{ agentStore.permissionRequest.impact }}</p><p class="subtle">所需权限:{{ permissionLabel(agentStore.permissionRequest.permission) }}({{ agentStore.permissionRequest.permission }})</p><pre>{{ JSON.stringify(localizeDetails(agentStore.permissionRequest.parameters), null, 2) }}</pre><div class="inline-actions permission-actions"><button class="button-primary" @click="agentStore.respondPermission('allow', 'once')">仅本次允许</button><button class="button-secondary" @click="agentStore.respondPermission('allow', 'session')">本次会话允许</button><button class="button-danger" @click="agentStore.respondPermission('deny')">拒绝</button></div></div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -106,6 +112,7 @@ function eventText(data: Record<string, unknown>) {
|
||||
.tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--space-sm); }
|
||||
.tool-option { display: flex; gap: var(--space-sm); padding: var(--space-sm); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); }
|
||||
.tool-option small { display: block; color: var(--color-text-secondary); }
|
||||
.tool-option code { display: block; margin: 2px 0; color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
|
||||
.network { display: flex; gap: var(--space-sm); }
|
||||
.trace-layout { display: grid; gap: var(--space-lg); }
|
||||
.run-summary, .event-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-md); }
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { runStatusLabel } from './labels'
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
const router = useRouter()
|
||||
const error = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
try { await agentStore.loadRuns() } catch (reason) { error.value = reason instanceof Error ? reason.message : 'Run 列表加载失败' }
|
||||
try { await agentStore.loadRuns() } catch (reason) { error.value = reason instanceof Error ? reason.message : '运行记录加载失败' }
|
||||
})
|
||||
|
||||
function selectRun(runId: string) { void router.push({ name: 'agent', params: { runId } }) }
|
||||
@@ -16,12 +17,12 @@ function selectRun(runId: string) { void router.push({ name: 'agent', params: {
|
||||
|
||||
<template>
|
||||
<div class="sidebar-panel">
|
||||
<button class="button-primary new-button" @click="router.push({ name: 'agent' })">+ 新建 Run</button>
|
||||
<button class="button-primary new-button" @click="router.push({ name: 'agent' })">+ 新建运行</button>
|
||||
<p v-if="error" class="subtle error-text">{{ error }}</p>
|
||||
<div class="sidebar-list">
|
||||
<button v-for="run in agentStore.sortedRuns" :key="run.run_id" class="sidebar-list-item run-item"
|
||||
:class="{ active: agentStore.activeRunId === run.run_id }" @click="selectRun(run.run_id)">
|
||||
<span class="badge" :class="{ success: run.status === 'completed', error: run.status === 'failed', warning: run.status === 'waiting_permission' }">{{ run.status }}</span>
|
||||
<span class="badge" :class="{ success: run.status === 'completed', error: run.status === 'failed', warning: run.status === 'waiting_permission' }">{{ runStatusLabel(run.status) }}</span>
|
||||
<strong>{{ run.run_id.slice(0, 12) }}</strong><small>{{ run.started_at ? new Date(run.started_at).toLocaleString() : '等待开始' }}</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
eventLabel,
|
||||
localizeDetails,
|
||||
permissionLabel,
|
||||
runStatusLabel,
|
||||
toolDescription,
|
||||
toolLabel,
|
||||
} from './labels'
|
||||
|
||||
describe('智能体页面中文标签', () => {
|
||||
it('转换运行状态和事件名称', () => {
|
||||
expect(runStatusLabel('waiting_permission')).toBe('等待授权')
|
||||
expect(eventLabel('ToolCall')).toBe('调用工具')
|
||||
})
|
||||
|
||||
it('转换工具、权限和工具说明', () => {
|
||||
expect(toolLabel('notes.search')).toBe('搜索笔记')
|
||||
expect(permissionLabel('notes.write')).toBe('修改笔记')
|
||||
expect(toolDescription('math.add', 'fallback')).toContain('两个数')
|
||||
expect(toolLabel('custom.tool')).toBe('custom.tool')
|
||||
})
|
||||
|
||||
it('递归转换事件详情中的键名、状态和布尔值', () => {
|
||||
expect(localizeDetails({
|
||||
tool_call_id: 'call-1',
|
||||
success: true,
|
||||
result: { status: 'completed' },
|
||||
})).toEqual({
|
||||
'工具调用 ID': 'call-1',
|
||||
'是否成功': '是',
|
||||
'结果': { '状态': '已完成' },
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { AgentEventType, AgentRunStatus } from '@/contracts'
|
||||
|
||||
const runStatusLabels: Record<AgentRunStatus, string> = {
|
||||
queued: '排队中',
|
||||
running: '运行中',
|
||||
waiting_permission: '等待授权',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
cancelled: '已取消',
|
||||
}
|
||||
|
||||
const eventLabels: Record<AgentEventType, string> = {
|
||||
RunStarted: '运行开始',
|
||||
TextDelta: '回复内容',
|
||||
ThinkingDelta: '思考过程',
|
||||
ToolCall: '调用工具',
|
||||
ToolResult: '工具结果',
|
||||
PermissionRequired: '请求权限',
|
||||
Usage: '用量统计',
|
||||
Citation: '引用来源',
|
||||
RunCompleted: '运行完成',
|
||||
RunFailed: '运行失败',
|
||||
RunCancelled: '运行取消',
|
||||
}
|
||||
|
||||
const toolLabels: Record<string, string> = {
|
||||
'system.echo': '回显测试',
|
||||
'math.add': '数值相加',
|
||||
'notes.search': '搜索笔记',
|
||||
'rag.search': '知识检索',
|
||||
'notes.read': '读取笔记',
|
||||
'notes.create': '创建笔记',
|
||||
'notes.update': '更新笔记',
|
||||
'notes.list': '列出笔记',
|
||||
'notes.move': '移动笔记',
|
||||
'tasks.create': '创建任务',
|
||||
'tasks.update': '更新任务',
|
||||
'tasks.list': '列出任务',
|
||||
'attachments.read': '读取附件',
|
||||
'audio.transcribe': '音频转写',
|
||||
}
|
||||
|
||||
const toolDescriptions: Record<string, string> = {
|
||||
'system.echo': '回显文本,用于本地智能体集成测试。',
|
||||
'math.add': '计算两个数的和,不产生外部副作用。',
|
||||
'notes.search': '搜索已建立索引的笔记,并返回摘要和引用。',
|
||||
'rag.search': '检索与当前任务相关的笔记内容块和引用。',
|
||||
'notes.read': '根据笔记 ID 读取笔记及其内容块。',
|
||||
'notes.create': '在当前知识库中创建 Markdown 笔记。',
|
||||
'notes.update': '更新已有 Markdown 笔记。',
|
||||
'notes.list': '按文件夹和标签筛选并列出笔记摘要。',
|
||||
'notes.move': '移动笔记到其他文件夹并保留笔记 ID。',
|
||||
'tasks.create': '创建并持久化任务。',
|
||||
'tasks.update': '更新已有任务。',
|
||||
'tasks.list': '列出已持久化的任务。',
|
||||
'attachments.read': '读取由宿主管理的 UTF-8 附件。',
|
||||
'audio.transcribe': '读取音频附件已有的宿主转写结果。',
|
||||
}
|
||||
|
||||
const permissionLabels: Record<string, string> = {
|
||||
'notes.search': '搜索笔记',
|
||||
'notes.read': '读取笔记',
|
||||
'notes.write': '修改笔记',
|
||||
'tasks.read': '读取任务',
|
||||
'tasks.write': '修改任务',
|
||||
'attachments.read': '读取附件',
|
||||
'network.request': '访问网络',
|
||||
'secrets.use': '使用密钥',
|
||||
}
|
||||
|
||||
const detailLabels: Record<string, string> = {
|
||||
tool_call_id: '工具调用 ID',
|
||||
name: '工具名称',
|
||||
arguments: '参数',
|
||||
parameters: '参数',
|
||||
success: '是否成功',
|
||||
output: '输出',
|
||||
result: '结果',
|
||||
error_code: '错误代码',
|
||||
error_message: '错误信息',
|
||||
note_id: '笔记 ID',
|
||||
block_id: '内容块 ID',
|
||||
heading_path: '标题路径',
|
||||
input_tokens: '输入令牌',
|
||||
output_tokens: '输出令牌',
|
||||
total_tokens: '令牌总数',
|
||||
status: '状态',
|
||||
duration_ms: '耗时(毫秒)',
|
||||
}
|
||||
|
||||
export function runStatusLabel(status?: AgentRunStatus): string {
|
||||
return status ? runStatusLabels[status] : '未知状态'
|
||||
}
|
||||
|
||||
export function eventLabel(event: AgentEventType): string {
|
||||
return eventLabels[event]
|
||||
}
|
||||
|
||||
export function toolLabel(name: string): string {
|
||||
return toolLabels[name] ?? name
|
||||
}
|
||||
|
||||
export function toolDescription(name: string, fallback: string): string {
|
||||
return toolDescriptions[name] ?? fallback
|
||||
}
|
||||
|
||||
export function permissionLabel(permission: string): string {
|
||||
return permissionLabels[permission] ?? permission
|
||||
}
|
||||
|
||||
function localizeValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(localizeValue)
|
||||
if (value && typeof value === 'object') return localizeDetails(value as Record<string, unknown>)
|
||||
if (value === true) return '是'
|
||||
if (value === false) return '否'
|
||||
if (typeof value === 'string' && value in runStatusLabels) {
|
||||
return runStatusLabels[value as AgentRunStatus]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function localizeDetails(data: Record<string, unknown>): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([key, value]) => [detailLabels[key] ?? key, localizeValue(value)])
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user