- - 证据
+ - {t('flame.detail.evidence')}
- {it.evidence}
{text && (
- - 经验 · 可直接贴进 Skill / 规范
+ - {t('flame.detail.experience')}
-
{text}
{it.steps && it.steps.length > 0 && (
- 共 {it.steps.length} 轮 · 单条轨迹证据,跨会话复现后可提升置信度
+ {t('flame.detail.stepsNote', { steps: it.steps.length })}
)}
@@ -332,6 +334,7 @@ function WasteDetailRow({ it }: { it: WasteItem }) {
// 优化提示词复制按钮(与准确性表同款)
function ExpCopyBtn({ text }: { text: string }) {
const [done, setDone] = useState(false);
+ const { t } = useI18n();
// HTTP 非安全上下文无 navigator.clipboard,copyText 内部自动降级
const handleCopy = (e: React.MouseEvent) => {
e.stopPropagation();
@@ -348,29 +351,30 @@ function ExpCopyBtn({ text }: { text: string }) {
? 'bg-green-100 text-green-600'
: 'bg-gray-100 hover:bg-gray-200 text-gray-500 hover:text-gray-700'
}`}
- title={done ? '已复制' : '复制优化提示词'}
+ title={done ? t('common.copied') : t('flame.copyPromptTitle')}
>
- {done ? '✓ 已复制' : '⧉ 复制'}
+ {done ? t('common.copied') : `⧉ ${t('common.copy')}`}
);
}
function WasteTable({ waste, wasteState }: { waste?: WasteReport | null; wasteState?: DimState }) {
const [open, setOpen] = useState (0);
+ const { t } = useI18n();
// 加载中 / 失败 / 未分析态
if (wasteState !== 'done' || !waste) {
return (
- 浪费剖析
+ {t('flame.waste.title')}
{wasteState === 'error' ? (
- 识别失败 —— LLM 调用出错,可稍后重试。
+ {t('flame.waste.error')}
) : wasteState === 'loading' ? (
- LLM 正在逐条判定候选是否值得优化…
+ {t('flame.waste.loading')}
) : (
- 尚未分析 —— 点击「重新分析」运行 LLM 浪费判定。
+ {t('flame.waste.notAnalyzed')}
)}
);
@@ -379,14 +383,12 @@ function WasteTable({ waste, wasteState }: { waste?: WasteReport | null; wasteSt
if (waste.items.length === 0) {
return (
- 浪费剖析
+ {t('flame.waste.title')}
{waste.considered === 0 ? (
-
- 无可评估的浪费候选 —— 轨迹过短或采集内容不足,未做判定(见上方提示)。
-
+ {t('flame.waste.noCandidates')}
) : (
- ✓ LLM 评估了 {waste.considered} 项候选,未发现值得优化的浪费。
+ {t('flame.waste.noWasteFound', { count: waste.considered })}
)}
@@ -395,22 +397,22 @@ function WasteTable({ waste, wasteState }: { waste?: WasteReport | null; wasteSt
return (
- 浪费剖析
+ {t('flame.waste.title')}
- | 现象 |
- 浪费类型 |
- 优化手段 |
- 置信度 |
- 优化提示词 |
+ {t('flame.waste.col.symptom')} |
+ {t('flame.waste.col.type')} |
+ {t('flame.waste.col.optimization')} |
+ {t('flame.waste.col.confidence')} |
+ {t('flame.waste.col.prompt')} |
{waste.items.map((it, i) => {
const isOpen = open === i;
- const expText = promptText(it);
+ const expText = promptText(t, it);
return (
| {it.optimization} |
-
- {it.confidence}
-
+ {(() => {
+ const raw = it.confidence;
+ const key =
+ raw === '\u9ad8'
+ ? 'flame.confidence.high'
+ : raw === '\u4e2d'
+ ? 'flame.confidence.medium'
+ : raw === '\u4f4e'
+ ? 'flame.confidence.low'
+ : null;
+ const label = key ? t(key as MessageKey) : raw;
+ const cls = CONF_CLS[raw] ?? CONF_CLS['\u4f4e'];
+ return (
+
+ {label}
+
+ );
+ })()}
|
@@ -452,12 +469,13 @@ function WasteTable({ waste, wasteState }: { waste?: WasteReport | null; wasteSt
function DetailPanel({ c, visCtx }: {
c: LLMCall; visCtx: Cat[];
}) {
+ const { t } = useI18n();
const total = ctxTotal(c);
return (
- Step #{c.step_id}
+ {t('flame.detail.stepTitle', { id: c.step_id })}
{c.time} · {c.label}
@@ -469,14 +487,14 @@ function DetailPanel({ c, visCtx }: {
return (
- {cat.label}
+ {t(cat.labelKey)}
{fmtK(v)} · {pct.toFixed(0)}%
);
})}
- 本轮输出
+ {t('flame.detail.currentOutput')}
↑{fmtK(c.output_tokens)}
diff --git a/src/agentsight/dashboard/src/i18n.tsx b/src/agentsight/dashboard/src/i18n.tsx
index 777d7f689a..40c4976383 100644
--- a/src/agentsight/dashboard/src/i18n.tsx
+++ b/src/agentsight/dashboard/src/i18n.tsx
@@ -14,7 +14,13 @@ export type Locale = (typeof SUPPORTED_LOCALES)[number];
const DEFAULT_LOCALE: Locale = 'en-US';
const LOCALE_STORAGE_KEY = 'agentsight.locale';
+// ─── Message catalog ──────────────────────────────────────────────────────────
+// Keys are flat dot-notation strings. Add new keys here first, then use them
+// in components via `t('section.key')`.
+// Placeholders use {name} syntax, resolved by the `t` function when `params` is passed.
+
const enUSMessages = {
+ // ── App / Nav / Login (existing) ──
'app.title': 'Agent Observability',
'app.loading': 'Loading...',
'language.label': 'Language',
@@ -41,13 +47,961 @@ const enUSMessages = {
'login.tokenHintSuffix': ' to view your token.',
'login.fullTokenHintPrefix': 'Or use ',
'login.fullTokenHintSuffix': ' to show the complete value.',
+
+ // ── Common / shared ──
+ 'common.loading': 'Loading...',
+ 'common.copy': 'Copy',
+ 'common.copied': '✓ Copied',
+ 'common.copyFullId': 'Copy full ID',
+ 'common.details': 'Details',
+ 'common.collapse': 'Collapse',
+ 'common.expandAll': 'Expand all →',
+ 'common.collapseAll': '← Collapse',
+ 'common.refresh': 'Refresh',
+ 'common.query': 'Query',
+ 'common.querying': 'Querying...',
+ 'common.agent': 'Agent',
+ 'common.allAgents': 'All Agents',
+ 'common.startTime': 'Start Time',
+ 'common.endTime': 'End Time',
+ 'common.last1h': 'Last 1h',
+ 'common.last6h': 'Last 6h',
+ 'common.last24h': 'Last 24h',
+ 'common.last7d': 'Last 7d',
+ 'common.noData': 'No data',
+ 'common.prev': 'Prev',
+ 'common.next': 'Next',
+ 'common.first': 'First',
+ 'common.last': 'Last',
+ 'common.page': 'Page {cur}/{total}',
+ 'common.perPage': 'Per page',
+ 'common.events': '{n} events',
+ 'common.steps': '{n} steps',
+ 'common.input': 'Input',
+ 'common.output': 'Output',
+ 'common.total': 'Total',
+ 'common.original': 'Original',
+ 'common.optimized': 'Optimized',
+ 'common.noChange': 'No change',
+ 'common.linesRemoved': '-{n} lines removed',
+ 'common.linesAdded': '+{n} lines added',
+ 'common.justNow': 'just now',
+ 'common.secondsAgo': '{n}s ago',
+ 'common.minutesAgo': '{n}m ago',
+ 'common.hoursAgo': '{n}h ago',
+ 'common.allTypes': 'All types',
+ 'common.allSeverities': 'All severities',
+ 'common.unresolvedOnly': 'Unresolved only',
+ 'common.resolved': 'Resolved',
+ 'common.unresolved': 'Unresolved',
+ 'common.resolve': 'Resolve',
+ 'common.time': 'Time',
+ 'common.type': 'Type',
+ 'common.severity': 'Severity',
+ 'common.session': 'Session',
+ 'common.conversation': 'Conversation',
+ 'common.status': 'Status',
+ 'common.actions': 'Actions',
+ 'common.critical': 'Critical',
+ 'common.high': 'High',
+ 'common.medium': 'Medium',
+ 'common.low': 'Low',
+ 'common.error': 'Error',
+ 'common.retry': 'Retry',
+ 'common.close': 'Close',
+ 'common.inOut': '{in} in / {out} out',
+ 'common.tokens': '{n} tokens',
+ 'common.min': 'min',
+ 'common.sec': 's',
+
+ // ── AgentHealthPage ──
+ 'ah.agentDashboard': 'Agent Dashboard',
+ 'ah.agentDashboardTooltip': 'Monitors the health of local AI agent processes. Crash records are shown only when a process crashed and affected an in-flight LLM conversation; normally-exiting processes are not displayed.',
+ 'ah.crashed': '{n} crashed',
+ 'ah.hungCount': '{n} hung',
+ 'ah.healthyRatio': '{healthy}/{total} healthy',
+ 'ah.lastScan': 'Last scan: {time}',
+ 'ah.noAgents': 'No agents discovered',
+ 'ah.deleteFailed': 'Delete failed: {msg}',
+ 'ah.restartSucceeded': '✅ Restart succeeded, new PID: {pid}; waiting for process to come online...',
+ 'ah.restartFailed': 'Restart failed: {msg}',
+ 'ah.interruptionEvents': 'Interruption Events',
+ 'ah.unresolvedCount': '{n} unresolved',
+ 'ah.noInterruptionEvents': 'No interruption events under the current filters',
+ 'ah.markResolvedConfirm': 'Once marked as resolved, this interruption will no longer count toward unresolved stats.\n\nMark as resolved?',
+ 'ah.markFailed': 'Mark failed: {msg}',
+ 'ah.noDetails': 'No details',
+ 'ah.relatedProcesses': 'Related processes ({n})',
+ 'ah.orphanedProcesses': 'Orphaned related processes ({n})',
+ 'ah.removeNow': 'Remove now',
+ 'ah.restarting': 'Restarting...',
+ 'ah.restartProcess': 'Restart process',
+ 'ah.gateway': 'Gateway',
+ 'ah.client': 'Client',
+ 'ah.worker': 'Worker',
+ 'ah.running': 'Running',
+ 'ah.untilAutoRemoval': '{time} until auto-removal',
+ 'ah.removingSoon': 'Removing soon',
+ 'ah.last1Hour': 'Last 1 hour',
+ 'ah.last24Hours': 'Last 24 hours',
+ 'ah.last7Days': 'Last 7 days',
+ 'ah.status.healthy': 'Healthy',
+ 'ah.status.unhealthy': 'Port unresponsive',
+ 'ah.status.hung': 'Hung',
+ 'ah.status.unknown': 'Pending check',
+ 'ah.status.noPort': 'Client process',
+ 'ah.status.offline': 'Crashed',
+ 'ah.tooltip.healthy': 'Service listens on its port and HTTP probe succeeds',
+ 'ah.tooltip.unhealthy': 'Port refuses connections; a restart may be needed',
+ 'ah.tooltip.hung': 'Port accepts connections but HTTP probe times out; process may be stuck',
+ 'ah.tooltip.unknown': 'First health check not yet completed',
+ 'ah.tooltip.noPort': 'TUI / child process without a service port (normal)',
+ 'ah.tooltip.offline': 'Process crashed and affected an in-flight LLM conversation; auto-removed after 5 minutes',
+ 'ah.tooltip.running': 'Single-process agent without a service port; running normally',
+ 'ah.requestFailed': 'Request failed',
+ 'ah.failedToLoad': 'Failed to load interruption events',
+ 'ah.callLabel': 'call: {id}',
+ 'ah.copiedValue': 'Copied: {value}',
+ 'ah.copyFailedValue': 'Copy failed: {value}',
+ 'ah.markResolvedTitle': 'Mark as resolved; no longer counted in unresolved stats',
+
+ // ── TokenSavingsPage ──
+ 'ts.actualConsumption': 'Actual Token Consumption',
+ 'ts.actualConsumptionTip': 'Tokens actually billed by the LLM API, already reflecting Tokenless optimization',
+ 'ts.savedTokens': 'Saved Tokens',
+ 'ts.savedTokensTip': 'Baseline = tokens that would have been consumed without Tokenless optimization. Saved = baseline - actual consumption',
+ 'ts.baseline': 'Baseline: {n}',
+ 'ts.savingsRate': 'Savings Rate',
+ 'ts.savingsRateFormula': '= Saved / Baseline × 100%',
+ 'ts.excellent': 'Excellent',
+ 'ts.good': 'Good',
+ 'ts.needsWork': 'Needs Work',
+ 'ts.baselineConsumption': 'Baseline consumption',
+ 'ts.actual': 'Actual',
+ 'ts.saved': 'Saved',
+ 'ts.noOptimizationRecords': 'No optimization records found',
+ 'ts.selectTimeRange': 'Select a time range and click "Query"',
+ 'ts.viewSavings': 'View token savings effects',
+ 'ts.optimizationTips': '🎯 Optimization Tips',
+ 'ts.savingsTop5': '📊 Savings Top 5 (by compounded savings)',
+ 'ts.category': 'Category',
+ 'ts.savingsStrategy': 'Savings Strategy',
+ 'ts.before': 'Before',
+ 'ts.optimizedCol': 'Optimized',
+ 'ts.savedCol': 'Saved',
+ 'ts.detailsCol': 'Details',
+ 'ts.compressionRatio': 'Compression ratio',
+ 'ts.affectsCalls': 'Affects {n} subsequent calls',
+ 'ts.compoundedSavings': 'Compounded savings {n} tokens',
+ 'ts.singleTurn': '(single-turn {pct}%)',
+ 'ts.sessionId': 'Session ID',
+ 'ts.inputTokens': 'Input Tokens',
+ 'ts.outputTokens': 'Output Tokens',
+ 'ts.savingsRateCol': 'Savings Rate',
+ 'ts.toolOutput': 'Tool Output',
+ 'ts.mcpOutput': 'MCP Output',
+ 'ts.schemaCompression': 'Schema Compression',
+ 'ts.responseCompression': 'Response Compression',
+ 'ts.commandRewrite': 'Command Rewrite',
+ 'ts.toonEncoding': 'TOON Encoding',
+ 'ts.schemaCompressionTip': 'Slim down tool/MCP interface definitions to shrink context',
+ 'ts.responseCompressionTip': 'Strip redundant response fields, keep semantically key content',
+ 'ts.commandRewriteTip': 'Rewrite tool commands into more compact equivalents',
+ 'ts.toonEncodingTip': 'Convert JSON output into compact TOON table text',
+ 'ts.savedComparedTooltip': 'Compared against this session\'s baseline consumption',
+ 'ts.tool': 'Tool',
+ 'ts.mcp': 'MCP',
+ 'ts.fetchFailed': 'Failed to fetch token savings',
+
+ // ── AtifViewerPage ──
+ 'atif.trajectoryViewer': 'Trajectory Viewer',
+ 'atif.downloadJson': '⬇️ Download JSON',
+ 'atif.importJson': '📁 Import JSON',
+ 'atif.bySession': 'By Session',
+ 'atif.byConversation': 'By Conversation',
+ 'atif.enterSessionId': 'Enter Session ID...',
+ 'atif.enterConversationId': 'Enter Conversation ID...',
+ 'atif.load': 'Load',
+ 'atif.loading': 'Loading...',
+ 'atif.enterSessionOrConv': 'Enter a Session or Conversation ID, then click "Load"',
+ 'atif.orImportLocal': 'or import a local ATIF JSON file',
+ 'atif.agentInfo': 'Agent Info',
+ 'atif.name': 'Name',
+ 'atif.version': 'Version',
+ 'atif.model': 'Model',
+ 'atif.toolDefinitions': 'Tool definitions',
+ 'atif.totalSteps': 'Total Steps',
+ 'atif.totalInputTokens': 'Total Input Tokens',
+ 'atif.totalOutputTokens': 'Total Output Tokens',
+ 'atif.ofWhichCached': 'of which cached: {n}',
+ 'atif.tokenSavingsComparison': 'Token Savings Comparison',
+ 'atif.originalTokens': 'Original Tokens (unoptimized)',
+ 'atif.actualTokens': 'Actual Tokens (optimized)',
+ 'atif.interactionTrajectory': 'Interaction Trajectory',
+ 'atif.roundsSteps': '{rounds} rounds · {steps} steps',
+ 'atif.noStepData': 'No step data in this trajectory',
+ 'atif.clickRoundToView': 'Click a round on the left to view details',
+ 'atif.noMessageContent': 'No message content',
+ 'atif.reasoning': 'Reasoning',
+ 'atif.toolCall': 'Tool call',
+ 'atif.observation': 'Observation',
+ 'atif.noOutputContent': 'No output content',
+ 'atif.inputLabel': 'Input: {n}',
+ 'atif.outputLabel': 'Output: {n}',
+ 'atif.cacheLabel': 'Cache: {n}',
+ 'atif.optimizedTokens': 'Optimized -{n} tokens ({strategy})',
+ 'atif.collapseArgs': 'Collapse args',
+ 'atif.expandArgs': 'Expand args',
+ 'atif.subagentTrajectory': '🤖 Subagent trajectory',
+ 'atif.selectSubagentHelp': 'Select this subagent in the topology graph above to view its trajectory',
+ 'atif.round': 'Round {n}',
+ 'atif.preamble': 'Preamble',
+ 'atif.conversationDetails': 'Conversation Details',
+ 'atif.toolCalls': '🔧 {n} tool calls',
+ 'atif.system': 'System',
+ 'atif.user': 'User',
+ 'atif.agentLabel': 'Agent',
+ 'atif.jsonParseFailed': 'JSON parse failed, please check the file format',
+ 'atif.jsonParseFailedNotATIF': 'JSON parse failed: missing schema_version field or not ATIF format',
+ 'atif.sessionNotFound': 'Session not found: {id} (no eBPF capture records and no collected trajectory)',
+ 'atif.malformedCollected': 'Malformed collected trajectory: {id}',
+ 'atif.cannotResolveSub': 'Cannot resolve sub-trajectory reference: missing trajectory_id or trajectory_path',
+ 'atif.externalNotSupported': 'External sub-trajectory references are not supported yet: {path}',
+ 'atif.selectSubagentInGraph': 'Select this subagent in the topology graph above to view its trajectory',
+ 'atif.stepLabel': 'Step {n}',
+ 'atif.callLabel': 'call: {id}',
+ 'atif.savedLabel': 'Saved',
+ 'atif.originalLabel': 'Original',
+ 'atif.actualLabel': 'Actual',
+ 'atif.loadFailed': 'Load failed',
+
+ // ── ConversationList ──
+ 'cl.traceDetails': 'Trace Details',
+ 'cl.noDataForTrace': 'No data for this trace',
+ 'cl.loadingTraces': 'Loading traces...',
+ 'cl.noTraces': 'No traces in this session',
+ 'cl.noMessageData': 'No message data',
+ 'cl.conversationId': 'Conversation ID',
+ 'cl.userQuery': 'User Query',
+ 'cl.inputTokens': 'Input Tokens',
+ 'cl.outputTokens': 'Output Tokens',
+ 'cl.startTime': 'Start Time',
+ 'cl.actions': 'Actions',
+ 'cl.qualityEval': 'Quality Eval',
+ 'cl.interrupts': 'Interrupts',
+ 'cl.eval': 'Eval',
+ 'cl.loadFailed': 'Load failed',
+ 'cl.loadFailedTooltip': 'Failed to load evaluation, click to retry',
+ 'cl.sessions': 'Sessions',
+ 'cl.totalInputTokens': 'Total Input Tokens',
+ 'cl.totalOutputTokens': 'Total Output Tokens',
+ 'cl.interruptions': 'Interruptions',
+ 'cl.tokenTimeseries': 'Token Timeseries (Input / Output / Total)',
+ 'cl.modelTokenTimeseries': 'Model Token Timeseries (Stacked)',
+ 'cl.noTimeseriesData': 'No timeseries data',
+ 'cl.noModelTimeseriesData': 'No model timeseries data',
+ 'cl.sessionId': 'Session ID',
+ 'cl.agent': 'Agent',
+ 'cl.model': 'Model',
+ 'cl.conversations': 'Conversations',
+ 'cl.savedTokens': 'Saved Tokens',
+ 'cl.lastActive': 'Last Active',
+ 'cl.noSessions': 'No sessions in the selected time range',
+ 'cl.ensureServiceRunning': 'Ensure the agentsight service is running and writing data',
+ 'cl.unknownModel': 'unknown model',
+ 'cl.queryFailed': 'Query failed',
+ 'cl.loadingEllipsis': 'Loading...',
+ 'cl.totalTokens': 'Total Tokens',
+
+ // ── Security ──
+ 'sec.securityObservability': 'Security Observability',
+ 'sec.securityObservabilityDesc': 'Security Observability / agent-sec daemon',
+ 'sec.refresh': 'Refresh',
+ 'sec.overview': 'Overview',
+ 'sec.securityEvents': 'Security Events',
+ 'sec.fullChainEvents': 'Full-chain Events',
+ 'sec.currentState': 'Current state is {state}; security data view not loaded.',
+ 'sec.securityEventDetails': 'Security Event Details',
+ 'sec.loadingDetails': 'Loading details...',
+ 'sec.eventNoLongerExists': 'This security event no longer exists.',
+ 'sec.loadingSecurityStatus': 'Loading security observability status...',
+ 'sec.statusLoadFailed': 'Failed to load security observability status',
+ 'sec.refreshStatus': 'Refresh Status',
+ 'sec.refreshing': 'Refreshing...',
+ 'sec.daemonReachable': 'daemon reachable',
+ 'sec.disabled': 'disabled',
+ 'sec.daemonUnreachable': 'daemon unreachable',
+ 'sec.storeUnavailable': 'data unavailable',
+ 'sec.schemaMismatch': 'schema mismatch',
+ 'sec.ok': 'ok',
+ 'sec.empty': 'no data',
+ 'sec.partial': 'partial data',
+ 'sec.found': 'found',
+ 'sec.notFound': 'not found',
+ 'sec.apiRequestFailed': 'Security observability API request failed',
+ 'sec.sessionOverview': 'Session Overview',
+ 'sec.sessionOverviewDesc': 'Aggregates security event verdicts for the selected session in the current time range',
+ 'sec.currentRun': 'Current Run',
+ 'sec.observabilityEvents': 'Observability Events',
+ 'sec.securityEventsCount': 'Security Events',
+ 'sec.nonPassVerdicts': '{n} non-pass verdicts found',
+ 'sec.allPass': 'All pass',
+ 'sec.loadingTimeline': 'Loading timeline...',
+ 'sec.noTimelineData': 'No timeline data for this run.',
+ 'sec.daemon': 'agent-sec daemon',
+ 'sec.category': 'Category',
+ 'sec.result': 'Result',
+ 'sec.verdict': 'Verdict',
+ 'sec.detail.verdict': 'Verdict',
+ 'sec.detail.error': 'Error',
+ 'sec.detail.reason': 'Reason',
+ 'sec.detail.finding': 'Finding',
+ 'sec.session': 'Session',
+ 'sec.run': 'Run',
+ 'sec.call': 'Call',
+ 'sec.toolCall': 'Tool Call',
+ 'sec.trace': 'Trace',
+ 'sec.turns': 'Turns',
+ 'sec.eventType': 'Event Type',
+ 'sec.sessionId': 'Session ID',
+ 'sec.runId': 'Run ID',
+ 'sec.all': 'All',
+ 'sec.clear': 'Clear',
+ 'sec.redactedByDaemon': 'Sensitive fields were redacted by the agent-sec daemon.',
+ 'sec.truncatedBySize': 'Some fields were truncated due to size limits.',
+ 'sec.totalEvents': 'Total {n}',
+ 'sec.loadingEvents': 'Loading security events...',
+ 'sec.noEventsFiltered': 'No security events match the current filters',
+ 'sec.loadingSummary': 'Loading security summary...',
+ 'sec.affectedSessions': 'Affected Sessions',
+ 'sec.affectedRuns': 'Affected Runs',
+ 'sec.noEventsInRange': 'No security events in the selected range.',
+ 'sec.noEventsInRangeShort': 'No security events in the selected range',
+ 'sec.byCategory': 'By Category',
+ 'sec.byEventType': 'By Event Type',
+ 'sec.byResult': 'By Result',
+ 'sec.noCategoryData': 'No category data',
+ 'sec.noEventTypeData': 'No event type data',
+ 'sec.noResultData': 'No result data',
+ 'sec.recentSecurityEvents': 'Recent Security Events',
+ 'sec.observabilityHook': 'observability {hook}',
+ 'sec.redacted': 'redacted',
+ 'sec.truncated': 'truncated',
+ 'sec.matchReason': 'match {reason}',
+ 'sec.viewEventDetailsAria': 'View security event details {id}',
+ 'sec.correlatedEvents': '{n} correlated security events',
+ 'sec.sessionLabel': 'session {id}',
+ 'sec.runLabel': 'run {id}',
+ 'sec.toolLabel': 'tool {id}',
+ 'sec.metadataMetrics': 'metadata / metrics',
+ 'sec.selectSessionToAggregate': 'Select a session to aggregate verdicts',
+ 'sec.aggregatingVerdicts': 'Aggregating verdicts...',
+ 'sec.verdictAggregationFailed': 'Verdict aggregation failed',
+ 'sec.noSessionSelected': 'No session selected',
+ 'sec.noSecurityEventsShort': 'No security events',
+ 'sec.noVerdictsYet': 'No verdicts yet',
+ 'sec.allVerdictsPass': 'All verdicts pass',
+ 'sec.verdictCoverage': 'Based on {n} events with verdicts in the current time range',
+ 'sec.summaryNoEvents': 'No security events were recorded in the current time range; the security capability found nothing to display.',
+ 'sec.summaryChecksPerformed': 'The security capability performed {n} checks in the current time range.',
+ 'sec.noNonPassFound': 'No non-pass verdicts found',
+ 'sec.summaryAllPass': 'The security capability performed {n} checks in the current time range; all recorded verdicts are pass.',
+ 'sec.riskVerdictsFound': '{n} risk verdicts found',
+ 'sec.summaryRiskFound': 'The security capability performed {total} checks in the current time range; {nonPass} verdicts are not pass — risky operations need attention.',
+ 'sec.verdictsToReview': '{n} verdicts need review',
+ 'sec.summaryReviewRecommended': 'The security capability performed {total} checks in the current time range; {nonPass} verdicts are not pass — review recommended.',
+ 'sec.summaryNoVerdictDetails': 'The security capability performed {n} checks in the current time range, but no verdict details are available in the current sample.',
+ 'sec.capabilityVerdict': 'Security Capability Verdict',
+ 'sec.checksExecuted': 'Checks Executed',
+ 'sec.coveringSessionsRuns': 'Covering {sessions} sessions / {runs} runs',
+ 'sec.riskyOperationRatio': 'Risky Operation Ratio',
+ 'sec.verdictsNotPass': '{nonPass} / {total} verdicts not pass',
+ 'sec.needsAttention': 'Needs Attention',
+ 'sec.riskWarningCount': 'Risk {risk} / Warning {warning}',
+ 'sec.clusteredByVerdict': 'Clustered by Verdict',
+ 'sec.withVerdict': '{n} with verdict',
+ 'sec.noVerdictClusterData': 'No verdict cluster data',
+ 'sec.whatCapabilityDid': 'What the Capability Did',
+ 'sec.noCheckActionData': 'No check action data',
+ 'sec.executionStatus': 'Execution Status',
+ 'sec.noExecutionStatusData': 'No execution status data',
+
+ // ── Components ──
+ 'comp.eval.pass': 'Pass',
+ 'comp.eval.review': 'Review',
+ 'comp.eval.fail': 'Fail',
+ 'comp.eval.qualityScore': 'Quality score {n}',
+ 'comp.eval.evaluationFailed': 'Evaluation failed',
+ 'comp.eval.qualityEvaluation': 'Quality Evaluation',
+ 'comp.interrupt.markResolvedConfirm': 'Once marked as resolved, this interruption will no longer count toward unresolved stats (the badge number will decrease).\n\nMark as resolved?',
+ 'comp.interrupt.operationFailed': 'Operation failed, please retry later',
+ 'comp.interrupt.markResolvedTitle': 'Mark as resolved; no longer counted in unresolved stats',
+ 'comp.interrupt.unresolvedCount': '{n} unresolved',
+ 'comp.interrupt.interruptions': 'Interruptions',
+ 'comp.sessionIdHelp.ariaLabel': 'Session ID usage help',
+ 'comp.sessionIdHelp.title': 'Session ID Usage',
+ 'comp.sessionIdHelp.identifies': 'Uniquely identifies one agent session.',
+ 'comp.sessionIdHelp.uses': 'Uses:',
+ 'comp.sessionIdHelp.use1': '① Filter sessions in logs when troubleshooting',
+ 'comp.sessionIdHelp.use2': '② Look up session details via agentsight CLI / API',
+ 'comp.sessionIdHelp.use3': '③ Paste the ID in the "🔍 ATIF Viewer" page to see the full trace',
+ 'comp.sessionIdHelp.copyHint': 'After clicking "Copy" on the right, paste it into the "🔍 ATIF Viewer" in the top nav bar to query.',
+ 'comp.subagentTopology': '🤖 Subagent Topology',
+ 'comp.subagentCount': '{n} subagents · click a node to view its trajectory below',
+ 'comp.externalTrajectory': 'External trajectory ↗',
+ 'comp.externalTrajectoryDetail': 'External trajectory {id}',
+ 'comp.subagentSteps': '{n} steps',
+
+ // ── InterruptionPanel ──
+ 'comp.interrupt.noEvents': 'No interruption events recorded for this session.',
+ 'comp.interrupt.failedToLoad': 'Failed to load interruptions',
+ 'comp.interrupt.callLabel': 'call: {id}',
+ 'comp.interrupt.typeCount': '{type}: {count}',
+
+ // ── Interruption types (shared across pages) ──
+ 'itype.llm_error': 'LLM Error',
+ 'itype.sse_truncated': 'SSE Truncated',
+ 'itype.context_overflow': 'Context Overflow',
+ 'itype.agent_crash': 'Agent Crash',
+ 'itype.token_limit': 'Token Limit',
+ 'itype.rate_limit': 'Rate Limit',
+ 'itype.auth_error': 'Auth Error',
+ 'itype.network_timeout': 'Network Timeout',
+ 'itype.service_unavailable': 'Service Unavailable',
+ 'itype.safety_filter': 'Safety Filter',
+ 'itype.retry_storm': 'Retry Storm',
+ 'itype.dead_loop': 'Dead Loop',
+ 'itype.tool_failure': 'Tool Failure',
+ 'itype.empty_response': 'Empty Response',
+ 'itype.resource_exhaustion': 'Resource Exhaustion',
+ 'itype.slow_response': 'Slow Response',
+ 'itype.state_machine_error': 'State Machine Error',
+ 'itype.unauthorized_action': 'Unauthorized Action',
+
+ // ── EvaluationPanel ──
+ 'comp.eval.noResult': 'No evaluation result yet.',
+ 'comp.eval.startEval': 'Start Evaluation',
+ 'comp.eval.evaluating': 'Evaluating...',
+ 'comp.eval.forceEval': 'Force Evaluation',
+ 'comp.eval.pendingCalls': '{n} LLM calls are still pending.',
+ 'comp.eval.pendingWhenRan': '{n} LLM calls were still pending when evaluation ran.',
+ 'comp.eval.collapseDetails': 'Collapse details',
+ 'comp.eval.viewDetails': 'View details',
+ 'comp.eval.dimensions': 'Evaluation Dimensions',
+ 'comp.eval.findings': 'Findings',
+ 'comp.eval.noFindings': 'No findings.',
+ 'comp.eval.rootCausePrefix': 'Root cause:',
+ 'comp.eval.summary.pass': 'The conversation completed with no deterministic quality issues found.',
+ 'comp.eval.summary.warn': 'The conversation is usable but needs review: {cause}.',
+ 'comp.eval.summary.fail': 'Evaluation failed, main reason: {cause}.',
+ 'comp.eval.action.none': 'Review warnings and supporting evidence.',
+ 'comp.eval.action.noActionRequired': 'No immediate action required.',
+ 'comp.eval.action.no_final_answer': 'Inspect the last LLM call and server response parsing.',
+ 'comp.eval.action.interrupted_main_call': 'Check interruption evidence, fix runtime stability, then retry the session.',
+ 'comp.eval.action.agent_crash': 'Check agent health and crash diagnostics before retrying.',
+ 'comp.eval.action.runtime_error': 'Check model service errors, network stability, and retry behavior.',
+ 'comp.eval.action.tool_failure': 'Inspect failed tool calls and tool response parsing.',
+ 'comp.eval.action.safety_risk': 'Review safety-related findings before re-running the agent.',
+ 'comp.eval.action.loop_detected': 'Inspect repeated calls and tighten stop conditions.',
+ 'comp.eval.action.excessive_cost': 'Review prompts, tool outputs, and token savings opportunities.',
+ 'comp.eval.action.partial_snapshot': 'Wait for pending calls to finish, or keep the forced-evaluation flag.',
+ 'comp.eval.cause.none': 'No clear root cause found',
+ 'comp.eval.cause.no_final_answer': 'No final answer produced',
+ 'comp.eval.cause.interrupted_main_call': 'Main call interrupted',
+ 'comp.eval.cause.agent_crash': 'Agent crash',
+ 'comp.eval.cause.runtime_error': 'Runtime error',
+ 'comp.eval.cause.tool_failure': 'Tool call failure',
+ 'comp.eval.cause.safety_risk': 'Safety risk',
+ 'comp.eval.cause.loop_detected': 'Suspected loop calls',
+ 'comp.eval.cause.excessive_cost': 'Excessive cost',
+ 'comp.eval.cause.partial_snapshot': 'Snapshot incomplete',
+ 'comp.eval.dim.completion': 'Completion',
+ 'comp.eval.dim.runtime_health': 'Runtime Health',
+ 'comp.eval.dim.tool_use': 'Tool Use',
+ 'comp.eval.dim.efficiency': 'Efficiency',
+ 'comp.eval.dim.safety': 'Safety',
+ 'comp.eval.finding.no_final_answer': 'No final answer',
+ 'comp.eval.finding.interrupted_main_call': 'Main call interrupted',
+ 'comp.eval.finding.partial_snapshot': 'Snapshot incomplete',
+ 'comp.eval.finding.tool_failure': 'Tool failure',
+ 'comp.eval.finding.loop_detected': 'Suspected loop',
+ 'comp.eval.finding.llm_error': 'LLM error',
+ 'comp.eval.finding.sse_truncated': 'SSE truncated',
+ 'comp.eval.finding.network_timeout': 'Network timeout',
+ 'comp.eval.finding.service_unavailable': 'Service unavailable',
+ 'comp.eval.finding.agent_crash': 'Agent crash',
+
+ // ── OptimizationPage / OptimizationSettings ──
+ 'opt.error.requestFailedStatus': 'Request failed ({status})',
+ 'opt.loading.analyzingTag': 'Analyzing',
+ 'opt.loading.calculating': 'Calculating, please wait...',
+ 'opt.error.analysisFailedHint': 'Analysis failed — you can click "Re-analyze" later to retry.',
+ 'opt.dim.notAnalyzedHint': 'This dimension has not been analyzed yet — click "Start analysis" in the top-right corner.',
+ 'opt.summary.generating': 'Generating trajectory summary…',
+ 'opt.summary.title': 'Trajectory Summary',
+ 'opt.summary.goalLabel': 'Goal',
+ 'opt.summary.processLabel': 'Process',
+ 'opt.summary.outcomeLabel': 'Outcome',
+ 'opt.copy.promptTitle': 'Copy optimization prompt',
+ 'opt.failure.toolErrorLabel': 'Tool error',
+ 'opt.failure.reasoningErrorLabel': 'Reasoning error',
+ 'opt.failure.timeoutLabel': 'Timeout',
+ 'opt.failure.invalidUsageLabel': 'Invalid usage',
+ 'opt.accuracy.failureRecovery': 'Recovery: {text}',
+ 'opt.accuracy.table.symptom': 'Symptom',
+ 'opt.accuracy.table.defectType': 'Defect type',
+ 'opt.accuracy.table.rootCause': 'Root cause object',
+ 'opt.accuracy.table.fixLocus': 'Fix locus',
+ 'opt.accuracy.table.confidence': 'Confidence',
+ 'opt.accuracy.table.prompt': 'Optimization prompt',
+ 'opt.accuracy.recoveredBadge': 'Recovered · optimization clue',
+ 'opt.accuracy.notOptimizable': 'Not optimizable',
+ 'opt.accuracy.detail.evidence': 'Evidence',
+ 'opt.accuracy.detail.verify': 'Verification',
+ 'opt.accuracy.detail.fix': 'Fix',
+ 'opt.accuracy.fixDiverges': 'Root cause at {primary}, fix at {fixLocus}',
+ 'opt.accuracy.fixLocusNone': 'None',
+ 'opt.accuracy.fixSame': 'Same as primary root cause',
+ 'opt.accuracy.sectionTitle': 'Accuracy analysis',
+ 'opt.accuracy.issueCountTag': '{count} issues',
+ 'opt.accuracy.noIssues': '✓ No issues detected that affect the final output accuracy.',
+ 'opt.accuracy.issueTableTitle': 'Failure list · click any row to expand root cause and fix',
+ 'opt.accuracy.issueTableTitleFallback': 'Failure list',
+ 'opt.accuracy.analyzeFailed': 'Accuracy analysis failed: {msg}',
+ 'opt.perf.state.error': 'Identification failed — LLM call error, please retry later.',
+ 'opt.perf.state.loading': 'LLM is analyzing performance data and choosing optimization strategies…',
+ 'opt.perf.state.idle': 'Not analyzed yet — click "Re-analyze" to run LLM strategy selection.',
+ 'opt.perf.state.noStrategies': '✓ LLM analysis completed, no applicable optimization strategies were found.',
+ 'opt.perf.table.symptom': 'Symptom',
+ 'opt.perf.table.strategyType': 'Strategy type',
+ 'opt.perf.table.rootCause': 'Root cause',
+ 'opt.perf.table.optimization': 'Optimization strategy',
+ 'opt.perf.sectionTitle': 'Performance analysis',
+ 'opt.perf.timeDistributionTitle': 'Time distribution',
+ 'opt.perf.slice.model': 'Model inference',
+ 'opt.perf.slice.tools': 'Tool execution',
+ 'opt.perf.slice.idle': 'User idle',
+ 'opt.perf.totalToolCalls': '{n} tool calls',
+ 'opt.perf.slowestCallsTitle': 'Slowest calls',
+ 'opt.perf.table.toolName': 'Tool',
+ 'opt.perf.table.duration': 'Duration',
+ 'opt.perf.table.command': 'Command',
+ 'opt.perf.noToolCalls': 'This session has no tool calls; all latency comes from model inference.',
+ 'opt.cost.sectionTitle': 'Cost analysis',
+ 'opt.cost.tag.headroomMeasured': '{tokens} tok · Headroom measured -{pct}%',
+ 'opt.cost.tag.headroom': '{tokens} tok',
+ 'opt.cost.tag.noHeadroom': '{events} events · {chars} characters',
+ 'opt.cost.meta': '{chars} characters · {events} events · {calls} LLM calls · {source}',
+ 'opt.cost.tokSourceMeasuredUsage': 'token measured (usage)',
+ 'opt.cost.tokSourceMeasuredSteps': 'token measured {used}/{total} steps',
+ 'opt.cost.tokSourceEstimated': 'token estimated',
+ 'opt.tab.accuracy.name': 'Accuracy',
+ 'opt.tab.accuracy.label': 'Accuracy analysis',
+ 'opt.tab.perf.name': 'Performance',
+ 'opt.tab.perf.label': 'Performance analysis',
+ 'opt.tab.cost.name': 'Cost',
+ 'opt.tab.cost.label': 'Cost analysis',
+ 'opt.tabs.ariaLabel': 'Dimension analysis',
+ 'opt.session.backToList': '← Back to session list',
+ 'opt.session.headerTitle': 'Optimization analysis · Session',
+ 'opt.session.viewSourceTrajectory': '🔍 View analyzed trajectory ↗',
+ 'opt.session.viewAnalysisTrajectory': '🤖 View analysis trajectory (agentsight-opt) ↗',
+ 'opt.session.action.analyzing': 'Analyzing...',
+ 'opt.session.action.reanalyze': 'Re-analyze',
+ 'opt.session.action.start': 'Start analysis',
+ 'opt.session.loadingHistory': 'Loading historical analysis results...',
+ 'opt.session.noAnalysisYet': 'This session has not been analyzed yet.',
+ 'opt.session.startHint': 'Click "Start analysis" to run accuracy / performance / cost analysis in parallel.',
+ 'opt.llm.notConfiguredHint': 'LLM is not configured — summary / performance strategies / cost waste / accuracy analysis all require LLM calls.',
+ 'opt.llm.goToSettings': 'Go to settings',
+ 'opt.summaryRow.issueCount': '{count} issues',
+ 'opt.summaryRow.accuracyLoading': 'Accuracy analysis in progress...',
+ 'opt.summaryRow.perf': '· {toolCount} tool calls · {seconds}s',
+ 'opt.summaryRow.cost': '· {events} events',
+ 'opt.dim.summary': 'Summary',
+ 'opt.dim.perf': 'Performance',
+ 'opt.dim.perfStrategy': 'Performance strategy',
+ 'opt.dim.cost': 'Cost',
+ 'opt.dim.costWaste': 'Cost waste',
+ 'opt.dim.accuracy': 'Accuracy',
+ 'opt.entry.subtitle': 'Enter a session ID to run accuracy / performance / cost analysis.',
+ 'opt.entry.sessionIdLabel': 'Session ID',
+ 'opt.entry.sessionIdPlaceholder': 'Paste a session ID and press Enter to start analysis',
+ 'opt.history.title': 'Historical analyses',
+ 'opt.history.count': '{n} records in total',
+ 'opt.history.limit': '(only the most recent {n} records are shown)',
+ 'opt.history.loadFailed': 'Failed to load history: {msg}',
+ 'opt.history.loading': 'Loading historical analyses...',
+ 'opt.history.empty': 'No analysis records yet',
+ 'opt.history.col.sessionId': 'Session ID',
+ 'opt.history.col.dimensions': 'Analyzed dimensions',
+ 'opt.history.col.firstAnalyzed': 'First analyzed at',
+ 'opt.history.col.lastUpdated': 'Last updated at',
+ 'opt.history.paginationSummary': '{count} results · page {page}/{total}',
+ 'opt.llm.provider.dashscope': 'Alibaba Cloud DashScope',
+ 'opt.llm.provider.openai': 'OpenAI',
+ 'opt.llm.provider.deepseek': 'DeepSeek',
+ 'opt.llm.provider.zhipu': 'Zhipu GLM',
+ 'opt.llm.provider.moonshot': 'Moonshot AI',
+ 'opt.llm.provider.custom': 'Custom endpoint',
+ 'opt.llm.model.glm52Reasoning': 'GLM 5.2 (reasoning)',
+ 'opt.llm.model.deepseekR1Reasoning': 'DeepSeek R1 (reasoning)',
+ 'opt.llm.model.o3Reasoning': 'o3 (reasoning)',
+ 'opt.llm.loadFailed': 'Failed to load configuration: {msg}',
+ 'opt.llm.saveFailed': 'Failed to save configuration: {msg}',
+ 'opt.llm.title': 'LLM configuration',
+ 'opt.llm.subtitle': 'Choose cloud provider and model; changes take effect immediately without restart. Used for optimization analysis (performance strategies / cost waste / accuracy).',
+ 'opt.llm.loading': 'Loading configuration...',
+ 'opt.llm.providerLabel': 'Cloud provider',
+ 'opt.llm.apiKey.placeholder': 'Enter API key',
+ 'opt.llm.apiKey.hide': 'Hide',
+ 'opt.llm.apiKey.show': 'Show',
+ 'opt.llm.apiKey.keepUnchanged': 'Leave empty to keep the current key unchanged.',
+ 'opt.llm.apiKey.hint': "Obtain the API key from the provider's console.",
+ 'opt.llm.modelLabel': 'Model',
+ 'opt.llm.model.customOption': '✏️ Custom model name...',
+ 'opt.llm.model.customPlaceholder': 'Enter model ID',
+ 'opt.llm.model.placeholder': 'Enter model ID, e.g. gpt-4o',
+ 'opt.llm.saving': 'Saving...',
+ 'opt.llm.save': 'Save configuration',
+ 'opt.llm.saved': '✓ Configuration saved and applied',
+
+ // ── TokenFlameChart ──
+ 'flame.ctx.static': 'Static region',
+ 'flame.ctx.user': 'User prompts',
+ 'flame.ctx.assistant': 'Assistant messages',
+ 'flame.ctx.tool': 'Tool outputs',
+ 'flame.ctx.others': 'Other injected context',
+ 'flame.title': 'Token Flame Chart',
+ 'flame.empty.noPerStepData': 'This session has no per-step cost data — it may be an older session or a failed trajectory parse. Re-run the analysis to generate the flame chart.',
+ 'flame.summary.totalTokens': 'Total Tokens',
+ 'flame.summary.totalTokensSub': 'in {input} · out {output}',
+ 'flame.summary.peakContext': 'Peak Context',
+ 'flame.summary.peakContextSub': 'step {step} · {time} · click to locate',
+ 'flame.chart.title': 'Context Window composition · per-step replay',
+ 'flame.chart.subtitle': 'Click a bar to view that step',
+ 'flame.legend.outputTokens': 'Output tokens',
+ 'flame.axis.contextLabel': 'Sent · context',
+ 'flame.waste.title': 'Waste Analysis',
+ 'flame.waste.error': 'Identification failed — the LLM call errored, please retry later.',
+ 'flame.waste.loading': 'The LLM is evaluating each candidate to see if it is worth optimizing…',
+ 'flame.waste.notAnalyzed': 'Not analyzed yet — click "Re-analyze" to run the LLM waste detection.',
+ 'flame.waste.noCandidates': 'No waste candidates to evaluate — the trajectory is too short or data insufficient; no evaluation was performed.',
+ 'flame.waste.noWasteFound': '✓ The LLM evaluated {count} candidates and found no waste worth optimizing.',
+ 'flame.waste.col.symptom': 'Symptom',
+ 'flame.waste.col.type': 'Waste type',
+ 'flame.waste.col.optimization': 'Optimization',
+ 'flame.waste.col.confidence': 'Confidence',
+ 'flame.waste.col.prompt': 'Optimization prompt',
+ 'flame.confidence.high': 'High',
+ 'flame.confidence.medium': 'Medium',
+ 'flame.confidence.low': 'Low',
+ 'flame.detail.evidence': 'Evidence',
+ 'flame.detail.experience': 'Experience · can be pasted directly into Skill/spec',
+ 'flame.detail.stepsNote': '{steps} rounds · single-trajectory evidence; reproducing across sessions will increase confidence',
+ 'flame.exp.applicability': 'Scenario: ',
+ 'flame.exp.pitfall': 'Wrong approach: ',
+ 'flame.exp.effectivePath': 'Correct approach: ',
+ 'flame.exp.rule': 'Convention: ',
+ 'flame.exp.badExample': 'Anti-pattern: ',
+ 'flame.exp.goodExample': 'Example: ',
+ 'flame.exp.scope': 'Scope: ',
+ 'flame.exp.symptom': 'Symptom: ',
+ 'flame.exp.optimization': 'Optimization: ',
+ 'flame.exp.evidence': 'Evidence: ',
+ 'flame.copyPromptTitle': 'Copy optimization prompt',
+ 'flame.detail.stepTitle': 'Step #{id}',
+ 'flame.detail.currentOutput': 'This step output',
+
+ // ── AgentSessionsPage ──
+ 'as.daysAgo': '{n} days ago',
+ 'as.timePreset.last24h': 'Last 24h',
+ 'as.timePreset.last7d': 'Last 7d',
+ 'as.timePreset.last30d': 'Last 30d',
+ 'as.source.ebpf': 'eBPF capture',
+ 'as.source.log': 'Log collection',
+ 'as.fetchError': 'Failed to fetch session list',
+ 'as.totalSessions': 'Total {n} sessions',
+ 'as.autoRefresh': 'Auto-refresh',
+ 'as.filter.allSources': 'All sources',
+ 'as.searchPlaceholder': 'Search session ID / project / message content...',
+ 'as.loadingSessions': 'Loading session list...',
+ 'as.noMatchingSessions': 'No matching sessions',
+ 'as.noSessionsInRange': 'No session data in the current time range',
+ 'as.source': 'Source',
+ 'as.project': 'Project',
+ 'as.firstMessage': 'First message',
+ 'as.lastMessage': 'Latest message',
+ 'as.lastActive': 'Last active',
+ 'as.copySessionId': 'Copy session ID',
+ 'as.subagentBadgeTitle': 'This session spawned {n} subagents',
+ 'as.subagentCount': '{n} subagents',
+ 'as.openInNewWindow': 'Click to view trajectory details in a new window',
+ 'as.metricsTooltip': 'Messages/steps {count} · Tokens {inTokens} / {outTokens}',
+ 'as.runOptimizationTitle': 'Run optimization analysis for this session',
+ 'as.analyze': '🔬 Analyze',
+ 'as.paginationSummary': '{total} results · Page {cur}/{totalPages}',
+
+ // ── Small shared components / SettingsPage ──
+ 'comp.agentHealth.crashToast': '⚠️ Agent "{name}" (PID {pid}) crashed and affected an in-flight conversation',
+ 'comp.agentHealth.hungToast': '⏳ Agent "{name}" (PID {pid}) timed out responding and may be hung',
+ 'comp.settings.title': '⚙️ Settings',
+ 'comp.settings.description': 'Manage the dashboard global configuration.',
+
+ // ── RiskEnforcementPage ──
+ 'risk.error.requestFailed': 'Request failed',
+ 'risk.mode.observe': 'Observe',
+ 'risk.mode.audit': 'Audit',
+ 'risk.mode.enforce': 'Enforce',
+ 'risk.bindingState.pending': 'Pending deployment',
+ 'risk.bindingState.enforced': 'Enforcing',
+ 'risk.bindingState.failed': 'Failed',
+ 'risk.bindingState.degraded': 'Degraded',
+ 'risk.bindingState.detaching': 'Detaching',
+ 'risk.bindingState.detached': 'Detached',
+ 'risk.effect.notify': 'Logged',
+ 'risk.effect.block': 'Blocked',
+ 'risk.effect.kill': 'Terminated',
+ 'risk.operation.invalidPid': 'PID must be a positive integer',
+ 'risk.operation.createSucceeded': 'Policy applied successfully',
+ 'risk.operation.detachSucceeded': 'Policy detached successfully',
+ 'risk.detach.confirm': 'Are you sure you want to detach this risk enforcement policy?',
+ 'risk.readiness.ready': 'Running',
+ 'risk.readiness.unavailable': 'Unavailable',
+ 'risk.readiness.checking': 'Checking',
+ 'risk.capability.loading': 'Reading enforcer capabilities and readiness...',
+ 'risk.capability.testDev': 'Test / development enforcer: includes simulated enforcement and policy handoff',
+ 'risk.capability.prod': 'Production enforcer: supports observe/audit only; credential enforcement and policy handoff are not available',
+ 'risk.title.enforce': 'Risk Enforcement',
+ 'risk.title.observeAudit': 'Risk Observation & Audit',
+ 'risk.subtitle': 'Use sensitive files as data sources, propagate taint to outgoing network traffic, and apply policies progressively (observe → audit → enforce).',
+ 'risk.refresh.loading': 'Refreshing...',
+ 'risk.summary.enforcerStatus': 'Enforcer status',
+ 'risk.summary.backend': 'Enforcer backend',
+ 'risk.summary.activeBindings': 'Active policies',
+ 'risk.summary.blockedViolations': 'Blocked violations',
+ 'risk.summary.auditedViolations': 'Audited violations',
+ 'risk.bindings.title': 'Policy bindings',
+ 'risk.bindings.header.agentPid': 'Agent / PID',
+ 'risk.bindings.header.sourcePath': 'Sensitive file',
+ 'risk.bindings.header.modeRevision': 'Mode / revision',
+ 'risk.bindings.header.state': 'State',
+ 'risk.bindings.header.actions': 'Actions',
+ 'risk.bindings.empty': 'No policy bindings yet',
+ 'risk.bindings.sourcePathAria': 'Policy path for binding {bindingId}',
+ 'risk.bindings.revisionLabel': 'Revision #{revision}',
+ 'risk.bindings.detachAria': 'Detach policy for Agent {agentId} (PID {pid}) on {path}',
+ 'risk.bindings.detaching': 'Detaching...',
+ 'risk.bindings.detach': 'Detach policy',
+ 'risk.form.title': 'Issue sensitive data exfiltration policy',
+ 'risk.form.sourcePathLabel': 'Sensitive file',
+ 'risk.form.modeLabel': 'Policy mode',
+ 'risk.form.scopeLabel': 'Target scope',
+ 'risk.form.trustedEndpointLabel': 'Trusted endpoint (optional)',
+ 'risk.form.sessionIdLabel': 'Session ID (optional)',
+ 'risk.form.scopeHelp': 'Only globally routable public IPv4; IPv6 and special-purpose addresses are not currently covered.',
+ 'risk.form.mode.observeOption': 'Observe: build evidence chain without affecting operations',
+ 'risk.form.mode.auditOption': 'Audit (recommended): evaluate rules and log without blocking',
+ 'risk.form.mode.enforceOption': 'Enforce: not supported by the current enforcer',
+ 'risk.form.mode.unreadyWarning': 'The current enforcer is not ready; policies cannot be issued.',
+ 'risk.form.mode.unsupportedWarning': 'The current enforcer has not declared support for this mode; policies cannot be issued.',
+ 'risk.form.bindingLimitWarning': 'The current ActPlane backend supports at most {maxActiveBindings} active policies; please detach existing policies before issuing new ones.',
+ 'risk.form.submit.loading': 'Issuing...',
+ 'risk.form.submit': 'Issue policy',
+ 'risk.violations.title': 'Policy hit records',
+ 'risk.violations.linkToAudit': 'View evidence chain in System Audit',
+ 'risk.violations.header.time': 'Time',
+ 'risk.violations.header.agentPid': 'Agent / PID',
+ 'risk.violations.header.operation': 'Operation',
+ 'risk.violations.header.target': 'Target',
+ 'risk.violations.header.result': 'Result',
+ 'risk.violations.header.reason': 'Reason',
+ 'risk.violations.empty': 'No enforcement records yet',
+ 'risk.violations.result.blocked': 'Blocked',
+ 'risk.violations.result.killed': 'Killed',
+ 'risk.violations.result.logged': 'Logged',
+
+ // ── ContainmentDialog / ContainmentLifecycleCard ──
+ 'cont.error.sourcePolicyUnavailable': 'Source policy is no longer available; cannot safely generate enforcement rules.',
+ 'cont.error.rootProcessStale': 'Target process has changed; refresh and select an online Agent.',
+ 'cont.error.ambiguousCandidate': 'Target process identity is not unique; refresh Agent status and retry.',
+ 'cont.error.caseNotEligible': 'Current case status does not allow upgrading to enforcement.',
+ 'cont.error.caseEligibilityChanged': 'Case status has changed; close the dialog and refresh the case.',
+ 'cont.error.invalidDuration': 'Enforcement duration is outside the server-allowed range.',
+ 'cont.error.incompatibleAction': 'This case already has a different enforcement action; review the existing action first.',
+ 'cont.error.actionInProgress': 'Enforcement policy is being issued; refresh case status later.',
+ 'cont.error.actionExpiring': 'Existing enforcement policy is being lifted; retry later.',
+ 'cont.error.cleanupRequired': 'Old policies still need cleanup; retry later or contact an administrator.',
+ 'cont.error.enforcerUnavailable': 'Kernel enforcement service is temporarily unavailable; retry later.',
+ 'cont.error.containmentDisabled': 'Risk containment is disabled in the current environment.',
+ 'cont.error.recoveryFailed': 'Existing enforcement action failed to recover; handle that action first.',
+ 'cont.error.healthStoreUnavailable': 'Online Agent health state is temporarily unavailable; retry later.',
+ 'cont.error.retryable': 'Request temporarily failed, please retry later.',
+ 'cont.error.nonRetryable': 'Cannot complete containment; please refresh case status.',
+ 'cont.error.generic': 'Request failed, please retry later.',
+ 'cont.existing.pending.title': 'Policy is being issued',
+ 'cont.existing.pending.message': 'The system is waiting for kernel enforcer confirmation; check later in case details.',
+ 'cont.existing.activePersistent.title': 'Persistent enforcement is active',
+ 'cont.existing.activePersistent.message': 'This case already has a persistent kernel policy; review or lift it in case details.',
+ 'cont.existing.activeTemporary.title': 'Temporary enforcement is active',
+ 'cont.existing.activeTemporary.message': 'This case already has a temporary kernel policy; check its expiry time in case details.',
+ 'cont.existing.expiring.title': 'Policy is being lifted',
+ 'cont.existing.expiring.message': 'Kernel policy is being cleaned up; cannot issue a new one until it completes.',
+ 'cont.existing.expired.title': 'Previous temporary enforcement expired; you can issue a new one.',
+ 'cont.existing.expired.message': 'A new action will still re-verify current online process identity.',
+ 'cont.existing.failed.title': 'Previous enforcement failed; you can retry.',
+ 'cont.existing.failed.message': 'Ensure the Agent is online and the kernel enforcement service has recovered.',
+ 'cont.unavailable.loading': 'Reading enforcer capabilities and readiness.',
+ 'cont.unavailable.notReady': 'Current enforcer is not ready; cannot safely issue kernel enforcement.',
+ 'cont.unavailable.noCredentialEnforce': 'Current enforcer does not declare credential enforcement and policy handoff; kernel enforcement cannot be issued.',
+ 'cont.dialog.title.available': 'Confirm upgrade to kernel enforcement',
+ 'cont.dialog.title.unavailable': 'Kernel enforcement unavailable',
+ 'cont.dialog.subtitle': "AgentSight will derive rules from the case's source policy; the dashboard does not receive or display policy DSL.",
+ 'cont.dialog.closeAria': 'Close',
+ 'cont.plan.loading': 'Loading containment plan...',
+ 'cont.plan.field.sourcePath': 'Sensitive file',
+ 'cont.plan.field.scope': 'Enforcement scope',
+ 'cont.plan.field.effect': 'Execution effect',
+ 'cont.plan.field.originalAgent': 'Original Agent',
+ 'cont.plan.scope.untrustedIpv4': 'Untrusted public IPv4 targets',
+ 'cont.plan.effect.actplaneDeny': 'ActPlane kernel deny',
+ 'cont.plan.original.unavailable': 'Original process unavailable',
+ 'cont.target.valid': 'Process identity valid: PID {pid}',
+ 'cont.target.selectLabel': 'Select an online process of the same Agent',
+ 'cont.target.placeholder': 'Select an online Agent',
+ 'cont.target.recheckHint': 'Original PID is invalid; the server will re-check start time and Agent identity before issuing.',
+ 'cont.target.noneAvailable': 'Original process identity is invalid, and no allowed online process of the same Agent was found.',
+ 'cont.duration.legend': 'Enforcement duration',
+ 'cont.duration.temporaryAria': 'Temporary enforcement {minutes} minutes',
+ 'cont.duration.temporaryTitle': 'Temporary enforcement {minutes} minutes',
+ 'cont.duration.temporaryDesc': 'Automatically lifted by AgentSight when it expires.',
+ 'cont.duration.persistentAria': 'Persistent enforcement (manual lift required)',
+ 'cont.duration.persistentTitle': 'Persistent enforcement (manual lift required)',
+ 'cont.duration.persistentDesc': 'Enabled only when explicitly selected; does not expire automatically.',
+ 'cont.footer.close': 'Close',
+ 'cont.footer.cancel': 'Cancel',
+ 'cont.footer.submit.loading': 'Issuing...',
+ 'cont.footer.submit': 'Confirm and issue',
+ 'cont.lifecycle.title': 'Risk containment status',
+ 'cont.lifecycle.sectionTitle': 'Risk containment',
+ 'cont.lifecycle.loading': 'Loading containment status...',
+ 'cont.lifecycle.error': 'Containment status is temporarily unavailable; refresh and retry.',
+ 'cont.lifecycle.empty': 'Pending upgrade: currently audit-only, not blocking system behavior.',
+ 'cont.lifecycle.upgrade.retry': 'Re-issue containment',
+ 'cont.lifecycle.upgrade': 'Upgrade to containment',
+ 'cont.lifecycle.field.targetProcess': 'Target process',
+ 'cont.lifecycle.field.binding': 'Policy binding',
+ 'cont.lifecycle.field.expiresAt': 'Expiry time',
+ 'cont.lifecycle.field.remaining': 'Remaining time',
+ 'cont.lifecycle.field.firstBlocked': 'First block time',
+ 'cont.lifecycle.field.failureStage': 'Failure stage',
+ 'cont.lifecycle.field.failureSummary': 'Failure summary',
+ 'cont.lifecycle.expires.persistent': 'Persistent',
+ 'cont.lifecycle.remaining.persistent': 'Manual lift required',
+ 'cont.lifecycle.markResolved': 'Mark as resolved',
+ 'cont.failureStage.attach': 'Policy attach',
+ 'cont.failureStage.detach': 'Policy detach',
+ 'cont.failureStage.reconcile': 'State reconciliation',
+ 'cont.remaining.waitRefresh': 'Waiting for status refresh',
+ 'cont.lifecycle.pending.label': 'Waiting to execute',
+ 'cont.lifecycle.pending.detail': 'Policy submitted; waiting for enforcer confirmation.',
+ 'cont.lifecycle.activeBlocked.label': 'Contained',
+ 'cont.lifecycle.activeBlocked.detail': 'Kernel has confirmed blocking.',
+ 'cont.lifecycle.activePending.label': 'Policy active',
+ 'cont.lifecycle.activePending.detail': 'Waiting for the first kernel block.',
+ 'cont.lifecycle.expiring.label': 'Being lifted',
+ 'cont.lifecycle.expiring.detail': 'Enforcer is cleaning up the policy.',
+ 'cont.lifecycle.expired.label': 'Expired',
+ 'cont.lifecycle.expired.detail': 'Temporary policy has been lifted.',
+ 'cont.lifecycle.failed.label': 'Execution failed',
+ 'cont.lifecycle.failed.detail': 'After confirming runtime health, you can re-issue the policy.',
+
+ // ── SystemAuditPage ──
+ 'audit.title': 'System Audit',
+ 'audit.badge.localData': 'AgentSight local data',
+ 'audit.description': 'Starting from Agent sessions, correlate tool calls, processes, files, network activity and ActPlane decisions to form a traceable evidence chain.',
+ 'audit.tab.overview': 'Audit overview',
+ 'audit.tab.sessions': 'Session audit',
+ 'audit.tab.cases': 'Risk cases',
+ 'audit.tab.events': 'Event search',
+ 'audit.stats.totalEvents.label': 'Audit events',
+ 'audit.stats.totalEvents.hint': 'Files / labels / network / decisions',
+ 'audit.stats.sessions.label': 'Related sessions',
+ 'audit.stats.sessions.hint': 'Sessions with system behavior evidence',
+ 'audit.stats.cases.label': 'Risk cases',
+ 'audit.stats.cases.hint': 'Cases formed after rule correlation',
+ 'audit.stats.open.label': 'Cases awaiting review',
+ 'audit.stats.open.hint': 'Awaiting security analyst confirmation',
+ 'audit.stats.blocked.label': 'Confirmed interceptions',
+ 'audit.stats.blocked.hint': 'Kernel has returned a deny result',
+ 'audit.button.viewRawEvents': 'View raw events',
+ 'audit.error.loadFailed': 'Failed to load system audit data',
+ 'audit.status.open': 'Pending review',
+ 'audit.status.confirmed': 'Confirmed',
+ 'audit.status.falsePositive': 'False positive',
+ 'audit.status.acceptedRisk': 'Accepted risk',
+ 'audit.status.resolved': 'Resolved',
+ 'audit.case.blocked': 'Blocked by kernel',
+ 'audit.case.notBlocked': 'Not confirmed blocked',
+ 'audit.evidence.loading': 'Loading evidence chain...',
+ 'audit.evidence.selectCasePlaceholder': 'Please select a risk case',
+ 'audit.case.summaryTitle': 'Risk conclusion',
+ 'audit.case.summaryLine': '{decision} · risk score {riskScore} · policy revision #{policyRevision}',
+ 'audit.button.confirmRisk': 'Confirm risk',
+ 'audit.button.markFalsePositive': 'Mark as false positive',
+ 'audit.button.acceptRisk': 'Accept risk',
+ 'audit.button.markResolved': 'Mark as resolved',
+ 'audit.evidence.fullChainTitle': 'Full evidence chain',
+ 'audit.event.fileAction': 'File read',
+ 'audit.event.taintTransition': 'Label propagation',
+ 'audit.event.networkAction': 'Network connection',
+ 'audit.event.policyDecision': 'Policy decision',
+ 'audit.event.enforcementState': 'Enforcement state',
+ 'audit.event.fallback': 'System event',
+ 'audit.evidence.fileFallback': 'Sensitive file access',
+ 'audit.evidence.networkFallback': 'Unknown network destination',
+ 'audit.evidence.policyFallback': 'Policy decision completed',
+ 'audit.evidence.systemEventFallback': 'System event',
+ 'audit.cases.empty': 'No risk cases',
+ 'audit.case.noSession': 'No session',
+ 'audit.cases.description': 'Select a case to view the complete, time-ordered raw evidence.',
+ 'audit.sessions.systemEvents': 'System events',
+ 'audit.sessions.timeRange': 'Time range',
+ 'audit.sessions.entryPoint': 'Entry',
+ 'audit.sessions.viewTimeline': 'View timeline',
+ 'audit.events.result': 'Result',
+
+ // ── SkillMetricsPage ──
+ 'skill.error.loadFailed': 'Failed to fetch skill metrics',
+ 'skill.concept.note': 'This page uses a single LLM call as the unit of statistics (each corresponding to one GenAI event record).',
+ 'skill.summary.eventCount': 'Analyzed calls',
+ 'skill.summary.discoveredSkills': 'Discovered skills',
+ 'skill.summary.totalLoads': 'Total loads',
+ 'skill.summary.usageRatio': 'Skill usage ratio',
+ 'skill.section.loads': 'Skill loads',
+ 'skill.section.skillsPerCall': 'Skill count per call distribution',
+ 'skill.distribution.axisLabel': 'Number of skills per call',
+ 'skill.distribution.min': 'Min',
+ 'skill.distribution.max': 'Max',
+ 'skill.distribution.mean': 'Mean',
+ 'skill.section.hotnessRanking': 'Skill hotness ranking',
+ 'skill.hotness.granularityLabel': 'Trend granularity:',
+ 'skill.hotness.day': 'By day',
+ 'skill.hotness.week': 'By week',
+ 'skill.hotness.rank': 'Rank',
+ 'skill.hotness.skillName': 'Skill',
+ 'skill.hotness.totalLoads': 'Total loads',
+ 'skill.hotness.trend': 'Trend',
} as const;
export type MessageKey = keyof typeof enUSMessages;
-const messages: Record> = {
+// Exported for the i18n regression test (placeholder consistency checks);
+// application code should always go through `t()`.
+export const messages: Record> = {
'en-US': enUSMessages,
'zh-CN': {
+ // ── App / Nav / Login ──
'app.title': 'Agent可观测',
'app.loading': '加载中...',
'language.label': '语言',
@@ -74,13 +1028,993 @@ const messages: Record> = {
'login.tokenHintSuffix': ' 查看令牌。',
'login.fullTokenHintPrefix': '或使用 ',
'login.fullTokenHintSuffix': ' 显示完整令牌。',
+
+ // ── Common ──
+ 'common.loading': '加载中...',
+ 'common.copy': '复制',
+ 'common.copied': '✓ 已复制',
+ 'common.copyFullId': '复制完整 ID',
+ 'common.details': '详情',
+ 'common.collapse': '收起',
+ 'common.expandAll': '展开全部 →',
+ 'common.collapseAll': '← 收起',
+ 'common.refresh': '刷新',
+ 'common.query': '查询',
+ 'common.querying': '查询中...',
+ 'common.agent': 'Agent',
+ 'common.allAgents': '全部 Agent',
+ 'common.startTime': '开始时间',
+ 'common.endTime': '结束时间',
+ 'common.last1h': '最近 1h',
+ 'common.last6h': '最近 6h',
+ 'common.last24h': '最近 24h',
+ 'common.last7d': '最近 7d',
+ 'common.noData': '暂无数据',
+ 'common.prev': '上一页',
+ 'common.next': '下一页',
+ 'common.first': '首页',
+ 'common.last': '末页',
+ 'common.page': '第 {cur}/{total} 页',
+ 'common.perPage': '每页',
+ 'common.events': '{n} 条事件',
+ 'common.steps': '{n} 步',
+ 'common.input': '输入',
+ 'common.output': '输出',
+ 'common.total': '总计',
+ 'common.original': '原始内容',
+ 'common.optimized': '优化后',
+ 'common.noChange': '无变更',
+ 'common.linesRemoved': '-{n} 行移除',
+ 'common.linesAdded': '+{n} 行新增',
+ 'common.justNow': '刚刚',
+ 'common.secondsAgo': '{n} 秒前',
+ 'common.minutesAgo': '{n} 分钟前',
+ 'common.hoursAgo': '{n} 小时前',
+ 'common.allTypes': '全部类型',
+ 'common.allSeverities': '全部严重级别',
+ 'common.unresolvedOnly': '仅未处理',
+ 'common.resolved': '已处理',
+ 'common.unresolved': '未处理',
+ 'common.resolve': '处理',
+ 'common.time': '时间',
+ 'common.type': '类型',
+ 'common.severity': '严重级别',
+ 'common.session': '会话',
+ 'common.conversation': '对话',
+ 'common.status': '状态',
+ 'common.actions': '操作',
+ 'common.critical': '严重',
+ 'common.high': '高',
+ 'common.medium': '中',
+ 'common.low': '低',
+ 'common.error': '错误',
+ 'common.retry': '重试',
+ 'common.close': '关闭',
+ 'common.inOut': '{in} in / {out} out',
+ 'common.tokens': '{n} tokens',
+ 'common.min': '分钟',
+ 'common.sec': '秒',
+
+ // ── AgentHealthPage ──
+ 'ah.agentDashboard': 'Agent 看板',
+ 'ah.agentDashboardTooltip': '监控本地 AI agent 进程的健康状态。仅当进程崩溃并影响了进行中的 LLM 对话时才显示崩溃记录;正常退出的进程不展示。',
+ 'ah.crashed': '{n} 已崩溃',
+ 'ah.hungCount': '{n} 卡住',
+ 'ah.healthyRatio': '{healthy}/{total} 正常',
+ 'ah.lastScan': '上次扫描: {time}',
+ 'ah.noAgents': '未发现 agent',
+ 'ah.deleteFailed': '删除失败: {msg}',
+ 'ah.restartSucceeded': '✅ 重启成功,新 PID: {pid},等待进程上线...',
+ 'ah.restartFailed': '重启失败: {msg}',
+ 'ah.interruptionEvents': '中断事件',
+ 'ah.unresolvedCount': '{n} 条未处理',
+ 'ah.noInterruptionEvents': '当前筛选条件下暂无中断事件',
+ 'ah.markResolvedConfirm': '标记为已处理后,此中断事件将不再计入未处理统计。\n\n确认标记为已处理吗?',
+ 'ah.markFailed': '标记失败: {msg}',
+ 'ah.noDetails': '无详情',
+ 'ah.relatedProcesses': '关联进程 ({n})',
+ 'ah.orphanedProcesses': '孤立关联进程 ({n})',
+ 'ah.removeNow': '立即移除',
+ 'ah.restarting': '重启中...',
+ 'ah.restartProcess': '重启进程',
+ 'ah.gateway': 'Gateway',
+ 'ah.client': '客户端',
+ 'ah.worker': 'Worker',
+ 'ah.running': '运行中',
+ 'ah.untilAutoRemoval': '{time}后自动移除',
+ 'ah.removingSoon': '即将移除',
+ 'ah.last1Hour': '最近 1 小时',
+ 'ah.last24Hours': '最近 24 小时',
+ 'ah.last7Days': '最近 7 天',
+ 'ah.status.healthy': '正常',
+ 'ah.status.unhealthy': '端口无响应',
+ 'ah.status.hung': '响应卡住',
+ 'ah.status.unknown': '待检测',
+ 'ah.status.noPort': '客户端进程',
+ 'ah.status.offline': '异常退出',
+ 'ah.tooltip.healthy': '服务监听端口且 HTTP 探活成功',
+ 'ah.tooltip.unhealthy': '端口不接受连接,可能需要重启',
+ 'ah.tooltip.hung': '端口可连但 HTTP 探活超时,进程可能卡死',
+ 'ah.tooltip.unknown': '首轮健康检查未完成',
+ 'ah.tooltip.noPort': 'TUI / 子进程,本身不提供服务端口(正常)',
+ 'ah.tooltip.offline': '进程异常退出,影响了进行中的 LLM 对话,5 分钟后自动移除',
+ 'ah.tooltip.running': '单进程 agent,本身不提供服务端口,运行正常',
+ 'ah.requestFailed': '请求失败',
+ 'ah.failedToLoad': '加载中断事件失败',
+ 'ah.callLabel': 'call: {id}',
+ 'ah.copiedValue': '已复制: {value}',
+ 'ah.copyFailedValue': '复制失败: {value}',
+ 'ah.markResolvedTitle': '标记为已处理,不再计入未处理统计',
+
+ // ── TokenSavingsPage ──
+ 'ts.actualConsumption': '实际 Token 消耗',
+ 'ts.actualConsumptionTip': 'LLM API 实际计费的 Token,已反映 Tokenless 优化效果',
+ 'ts.savedTokens': '已节省 Token',
+ 'ts.savedTokensTip': '基线 = 未启用 Tokenless 优化时的 Token 消耗。节省 = 基线 - 实际消耗',
+ 'ts.baseline': '基线: {n}',
+ 'ts.savingsRate': '节省率',
+ 'ts.savingsRateFormula': '= 节省 / 基线 × 100%',
+ 'ts.excellent': '优秀',
+ 'ts.good': '良好',
+ 'ts.needsWork': '待改进',
+ 'ts.baselineConsumption': '基线消耗',
+ 'ts.actual': '实际',
+ 'ts.saved': '节省',
+ 'ts.noOptimizationRecords': '未找到优化记录',
+ 'ts.selectTimeRange': '选择时间范围后点击"查询"',
+ 'ts.viewSavings': '查看 Token 节省效果',
+ 'ts.optimizationTips': '🎯 优化建议',
+ 'ts.savingsTop5': '📊 节省 Top 5(按复合节省排序)',
+ 'ts.category': '类别',
+ 'ts.savingsStrategy': '节省策略',
+ 'ts.before': '优化前',
+ 'ts.optimizedCol': '优化后',
+ 'ts.savedCol': '节省',
+ 'ts.detailsCol': '详情',
+ 'ts.compressionRatio': '压缩率',
+ 'ts.affectsCalls': '影响后续 {n} 轮调用',
+ 'ts.compoundedSavings': '复合节省 {n} tokens',
+ 'ts.singleTurn': '(单轮 {pct}%)',
+ 'ts.sessionId': 'Session ID',
+ 'ts.inputTokens': '输入 Tokens',
+ 'ts.outputTokens': '输出 Tokens',
+ 'ts.savingsRateCol': '节省率',
+ 'ts.toolOutput': '工具输出',
+ 'ts.mcpOutput': 'MCP输出',
+ 'ts.schemaCompression': 'Schema 压缩',
+ 'ts.responseCompression': '响应压缩',
+ 'ts.commandRewrite': '命令重写',
+ 'ts.toonEncoding': 'TOON 编码',
+ 'ts.schemaCompressionTip': '精简工具/MCP 接口定义,减少上下文体积',
+ 'ts.responseCompressionTip': '清理响应冗余字段,保留语义关键内容',
+ 'ts.commandRewriteTip': '将工具命令重写为更精简的等价形式',
+ 'ts.toonEncodingTip': '将 JSON 输出转换为紧凑 TOON 表格文本',
+ 'ts.savedComparedTooltip': '与该会话的基线消耗对比',
+ 'ts.tool': '工具',
+ 'ts.mcp': 'MCP',
+ 'ts.fetchFailed': '获取 Token 节省数据失败',
+
+ // ── AtifViewerPage ──
+ 'atif.trajectoryViewer': '轨迹查看',
+ 'atif.downloadJson': '⬇️ 下载 JSON',
+ 'atif.importJson': '📁 导入 JSON',
+ 'atif.bySession': '按 Session',
+ 'atif.byConversation': '按对话',
+ 'atif.enterSessionId': '输入 Session ID...',
+ 'atif.enterConversationId': '输入对话 ID...',
+ 'atif.load': '加载',
+ 'atif.loading': '加载中...',
+ 'atif.enterSessionOrConv': '输入 Session 或对话 ID,然后点击"加载"',
+ 'atif.orImportLocal': '或导入本地 ATIF JSON 文件',
+ 'atif.agentInfo': 'Agent 信息',
+ 'atif.name': '名称',
+ 'atif.version': '版本',
+ 'atif.model': '模型',
+ 'atif.toolDefinitions': '工具定义',
+ 'atif.totalSteps': '总步数',
+ 'atif.totalInputTokens': '总输入 Tokens',
+ 'atif.totalOutputTokens': '总输出 Tokens',
+ 'atif.ofWhichCached': '其中缓存命中: {n}',
+ 'atif.tokenSavingsComparison': 'Token 节省对比',
+ 'atif.originalTokens': '原始 Tokens(未优化)',
+ 'atif.actualTokens': '实际 Tokens(已优化)',
+ 'atif.interactionTrajectory': '交互轨迹',
+ 'atif.roundsSteps': '{rounds} 轮 · {steps} 步',
+ 'atif.noStepData': '该轨迹暂无步骤数据',
+ 'atif.clickRoundToView': '点击左侧的轮次查看详情',
+ 'atif.noMessageContent': '无消息内容',
+ 'atif.reasoning': '推理过程',
+ 'atif.toolCall': '工具调用',
+ 'atif.observation': '观测结果',
+ 'atif.noOutputContent': '无输出内容',
+ 'atif.inputLabel': '输入: {n}',
+ 'atif.outputLabel': '输出: {n}',
+ 'atif.cacheLabel': '缓存: {n}',
+ 'atif.optimizedTokens': '已优化 -{n} tokens ({strategy})',
+ 'atif.collapseArgs': '收起参数',
+ 'atif.expandArgs': '展开参数',
+ 'atif.subagentTrajectory': '🤖 子代理轨迹',
+ 'atif.selectSubagentHelp': '在上方拓扑图中选择此子代理以查看其轨迹',
+ 'atif.round': '第 {n} 轮',
+ 'atif.preamble': '前置',
+ 'atif.conversationDetails': '对话详情',
+ 'atif.toolCalls': '🔧 {n} 次工具调用',
+ 'atif.system': '系统',
+ 'atif.user': '用户',
+ 'atif.agentLabel': 'Agent',
+ 'atif.jsonParseFailed': 'JSON 解析失败,请检查文件格式',
+ 'atif.jsonParseFailedNotATIF': 'JSON 解析失败:缺少 schema_version 字段或不是 ATIF 格式',
+ 'atif.sessionNotFound': '未找到 Session: {id}(无 eBPF 捕获记录且无采集轨迹)',
+ 'atif.malformedCollected': '采集轨迹格式错误: {id}',
+ 'atif.cannotResolveSub': '无法解析子轨迹引用:缺少 trajectory_id 或 trajectory_path',
+ 'atif.externalNotSupported': '暂不支持外部子轨迹引用: {path}',
+ 'atif.selectSubagentInGraph': '在上方拓扑图中选择此子代理以查看其轨迹',
+ 'atif.stepLabel': '第 {n} 步',
+ 'atif.callLabel': 'call: {id}',
+ 'atif.savedLabel': '节省',
+ 'atif.originalLabel': '原始',
+ 'atif.actualLabel': '实际',
+ 'atif.loadFailed': '加载失败',
+
+ // ── ConversationList ──
+ 'cl.traceDetails': 'Trace 详情',
+ 'cl.noDataForTrace': '该 Trace 暂无数据',
+ 'cl.loadingTraces': '加载 traces...',
+ 'cl.noTraces': '该 session 暂无 traces',
+ 'cl.noMessageData': '无消息数据',
+ 'cl.conversationId': '对话 ID',
+ 'cl.userQuery': '用户提问',
+ 'cl.inputTokens': '输入 Tokens',
+ 'cl.outputTokens': '输出 Tokens',
+ 'cl.startTime': '开始时间',
+ 'cl.actions': '操作',
+ 'cl.qualityEval': '质量评估',
+ 'cl.interrupts': '中断',
+ 'cl.eval': '评估',
+ 'cl.loadFailed': '加载失败',
+ 'cl.loadFailedTooltip': '评估加载失败,点击重试',
+ 'cl.sessions': '会话数',
+ 'cl.totalInputTokens': '总输入 Tokens',
+ 'cl.totalOutputTokens': '总输出 Tokens',
+ 'cl.interruptions': '中断',
+ 'cl.tokenTimeseries': 'Token 时间序列(输入 / 输出 / 总计)',
+ 'cl.modelTokenTimeseries': '模型 Token 时间序列(堆叠)',
+ 'cl.noTimeseriesData': '暂无时间序列数据',
+ 'cl.noModelTimeseriesData': '暂无模型时间序列数据',
+ 'cl.sessionId': 'Session ID',
+ 'cl.agent': 'Agent',
+ 'cl.model': '模型',
+ 'cl.conversations': '对话数',
+ 'cl.savedTokens': '已节省 Tokens',
+ 'cl.lastActive': '最后活跃',
+ 'cl.noSessions': '所选时间范围内暂无会话',
+ 'cl.ensureServiceRunning': '请确认 agentsight 服务正在运行并写入数据',
+ 'cl.unknownModel': '未知模型',
+ 'cl.queryFailed': '查询失败',
+ 'cl.loadingEllipsis': '加载中...',
+ 'cl.totalTokens': '总 Token 数',
+
+ // ── Security ──
+ 'sec.securityObservability': '安全可观测',
+ 'sec.securityObservabilityDesc': 'Security Observability / agent-sec daemon',
+ 'sec.refresh': '刷新',
+ 'sec.overview': '概览',
+ 'sec.securityEvents': '安全事件',
+ 'sec.fullChainEvents': '全链路事件',
+ 'sec.currentState': '当前状态为 {state},安全数据视图未加载。',
+ 'sec.securityEventDetails': '安全事件详情',
+ 'sec.loadingDetails': '加载详情...',
+ 'sec.eventNoLongerExists': '该安全事件已不存在。',
+ 'sec.loadingSecurityStatus': '加载安全观测状态...',
+ 'sec.statusLoadFailed': '安全观测状态加载失败',
+ 'sec.refreshStatus': '刷新状态',
+ 'sec.refreshing': '刷新中...',
+ 'sec.daemonReachable': 'daemon 可达',
+ 'sec.disabled': '已禁用',
+ 'sec.daemonUnreachable': 'daemon 不可达',
+ 'sec.storeUnavailable': '数据不可用',
+ 'sec.schemaMismatch': 'schema 不兼容',
+ 'sec.ok': '正常',
+ 'sec.empty': '无数据',
+ 'sec.partial': '部分数据',
+ 'sec.found': '已找到',
+ 'sec.notFound': '未找到',
+ 'sec.apiRequestFailed': '安全观测接口请求失败',
+ 'sec.sessionOverview': 'Session 总览',
+ 'sec.sessionOverviewDesc': '按当前时间范围统计所选 session 的安全事件 verdict',
+ 'sec.currentRun': '当前 Run',
+ 'sec.observabilityEvents': '观测事件',
+ 'sec.securityEventsCount': '安全事件',
+ 'sec.nonPassVerdicts': '存在 {n} 个非 pass verdict',
+ 'sec.allPass': '全部 pass',
+ 'sec.loadingTimeline': '加载 timeline...',
+ 'sec.noTimelineData': '该 run 暂无 timeline 数据。',
+ 'sec.daemon': 'agent-sec daemon',
+ 'sec.category': '类别',
+ 'sec.result': '结果',
+ 'sec.verdict': 'Verdict',
+ 'sec.detail.verdict': 'Verdict',
+ 'sec.detail.error': '错误',
+ 'sec.detail.reason': '原因',
+ 'sec.detail.finding': '发现',
+ 'sec.session': 'Session',
+ 'sec.run': 'Run',
+ 'sec.call': 'Call',
+ 'sec.toolCall': '工具调用',
+ 'sec.trace': 'Trace',
+ 'sec.turns': 'Turns',
+ 'sec.eventType': '事件类型',
+ 'sec.sessionId': 'Session ID',
+ 'sec.runId': 'Run ID',
+ 'sec.all': '全部',
+ 'sec.clear': '清空',
+ 'sec.redactedByDaemon': '敏感字段已由 agent-sec daemon 脱敏。',
+ 'sec.truncatedBySize': '部分字段因大小限制被截断。',
+ 'sec.totalEvents': '共 {n} 条',
+ 'sec.loadingEvents': '加载安全事件...',
+ 'sec.noEventsFiltered': '所选过滤条件下暂无安全事件',
+ 'sec.loadingSummary': '加载安全汇总...',
+ 'sec.affectedSessions': '影响 Session',
+ 'sec.affectedRuns': '影响 Run',
+ 'sec.noEventsInRange': '所选范围内暂无安全事件。',
+ 'sec.noEventsInRangeShort': '所选范围内暂无安全事件',
+ 'sec.byCategory': '按类别',
+ 'sec.byEventType': '按事件类型',
+ 'sec.byResult': '按结果',
+ 'sec.noCategoryData': '暂无类别数据',
+ 'sec.noEventTypeData': '暂无事件类型数据',
+ 'sec.noResultData': '暂无结果数据',
+ 'sec.recentSecurityEvents': '近期安全事件',
+ 'sec.observabilityHook': 'observability {hook}',
+ 'sec.redacted': '已脱敏',
+ 'sec.truncated': '已截断',
+ 'sec.matchReason': '命中 {reason}',
+ 'sec.viewEventDetailsAria': '查看安全事件详情 {id}',
+ 'sec.correlatedEvents': '关联安全事件 {n} 条',
+ 'sec.sessionLabel': 'session {id}',
+ 'sec.runLabel': 'run {id}',
+ 'sec.toolLabel': 'tool {id}',
+ 'sec.metadataMetrics': '元数据 / 指标',
+ 'sec.selectSessionToAggregate': '选择 Session 后统计 verdict',
+ 'sec.aggregatingVerdicts': 'Verdict 统计中...',
+ 'sec.verdictAggregationFailed': 'Verdict 统计失败',
+ 'sec.noSessionSelected': '未选择 Session',
+ 'sec.noSecurityEventsShort': '无安全事件',
+ 'sec.noVerdictsYet': '暂无 verdict',
+ 'sec.allVerdictsPass': '全部 verdict 为 pass',
+ 'sec.verdictCoverage': '基于当前时间范围内 {n} 条含 verdict 事件统计',
+ 'sec.summaryNoEvents': '当前时间范围内未记录安全事件,安全能力没有发现需要展示的检查结果。',
+ 'sec.summaryChecksPerformed': '安全能力在当前时间范围内执行了 {n} 次检查。',
+ 'sec.noNonPassFound': '未发现非 pass verdict',
+ 'sec.summaryAllPass': '安全能力在当前时间范围内执行了 {n} 次检查,已记录 verdict 的事件均为 pass。',
+ 'sec.riskVerdictsFound': '存在 {n} 个风险 verdict',
+ 'sec.summaryRiskFound': '安全能力在当前时间范围内执行了 {total} 次检查,其中 {nonPass} 个 verdict 不是 pass,需要关注风险操作。',
+ 'sec.verdictsToReview': '存在 {n} 个待关注 verdict',
+ 'sec.summaryReviewRecommended': '安全能力在当前时间范围内执行了 {total} 次检查,其中 {nonPass} 个 verdict 不是 pass,建议复核。',
+ 'sec.summaryNoVerdictDetails': '安全能力在当前时间范围内执行了 {n} 次检查,但当前样本中暂无 verdict 明细。',
+ 'sec.capabilityVerdict': '安全能力结论',
+ 'sec.checksExecuted': '安全能力执行',
+ 'sec.coveringSessionsRuns': '覆盖 {sessions} Session / {runs} Run',
+ 'sec.riskyOperationRatio': '风险操作占比',
+ 'sec.verdictsNotPass': '{nonPass} / {total} 个 verdict 非 pass',
+ 'sec.needsAttention': '需要关注',
+ 'sec.riskWarningCount': '风险 {risk} / Warning {warning}',
+ 'sec.clusteredByVerdict': '按 Verdict 聚类',
+ 'sec.withVerdict': '{n} 条含 verdict',
+ 'sec.noVerdictClusterData': '暂无 verdict 聚类数据',
+ 'sec.whatCapabilityDid': '安全能力做了什么',
+ 'sec.noCheckActionData': '暂无检查动作数据',
+ 'sec.executionStatus': '执行状态',
+ 'sec.noExecutionStatusData': '暂无执行状态数据',
+
+ // ── Components ──
+ 'comp.eval.pass': '通过',
+ 'comp.eval.review': '需复核',
+ 'comp.eval.fail': '未通过',
+ 'comp.eval.qualityScore': '质量分 {n}',
+ 'comp.eval.evaluationFailed': '质量评估失败',
+ 'comp.eval.qualityEvaluation': '质量评估',
+ 'comp.interrupt.markResolvedConfirm': '标记为已处理后,此中断事件将不再计入未处理统计(badge 数字将减少)。\n\n确认标记为已处理吗?',
+ 'comp.interrupt.operationFailed': '操作失败,请稍后重试',
+ 'comp.interrupt.markResolvedTitle': '标记为已处理,不再计入未处理统计',
+ 'comp.interrupt.unresolvedCount': '{n} 条未处理',
+ 'comp.interrupt.interruptions': '中断事件',
+ 'comp.sessionIdHelp.ariaLabel': 'Session ID 用法说明',
+ 'comp.sessionIdHelp.title': 'Session ID 用法',
+ 'comp.sessionIdHelp.identifies': '唯一标识一次 Agent 会话。',
+ 'comp.sessionIdHelp.uses': '用途:',
+ 'comp.sessionIdHelp.use1': '① 排查问题时在日志里过滤会话',
+ 'comp.sessionIdHelp.use2': '② 通过 agentsight CLI / API 检索会话详情',
+ 'comp.sessionIdHelp.use3': '③ 在「🔍 ATIF 查看器」页面粘入 ID 查看完整 trace',
+ 'comp.sessionIdHelp.copyHint': '点击右侧「复制」后,可在顶部导航栏「🔍 ATIF 查看器」中粘贴查询。',
+ 'comp.subagentTopology': '🤖 子代理拓扑',
+ 'comp.subagentCount': '共 {n} 个子代理 · 点击节点在下方查看其轨迹',
+ 'comp.externalTrajectory': '外部轨迹 ↗',
+ 'comp.externalTrajectoryDetail': '外部轨迹 {id}',
+ 'comp.subagentSteps': '{n} 步',
+
+ // ── InterruptionPanel ──
+ 'comp.interrupt.noEvents': '该会话暂无中断事件记录。',
+ 'comp.interrupt.failedToLoad': '加载中断事件失败',
+ 'comp.interrupt.callLabel': 'call: {id}',
+ 'comp.interrupt.typeCount': '{type}:{count} 次',
+
+ // ── Interruption types (shared across pages) ──
+ 'itype.llm_error': 'LLM 错误',
+ 'itype.sse_truncated': 'SSE 截断',
+ 'itype.context_overflow': '上下文溢出',
+ 'itype.agent_crash': 'Agent 崩溃',
+ 'itype.token_limit': 'Token 超限',
+ 'itype.rate_limit': '速率限制',
+ 'itype.auth_error': '鉴权错误',
+ 'itype.network_timeout': '网络超时',
+ 'itype.service_unavailable': '服务不可用',
+ 'itype.safety_filter': '安全过滤',
+ 'itype.retry_storm': '重试风暴',
+ 'itype.dead_loop': '死循环',
+ 'itype.tool_failure': '工具调用失败',
+ 'itype.empty_response': '空响应',
+ 'itype.resource_exhaustion': '资源耗尽',
+ 'itype.slow_response': '响应过慢',
+ 'itype.state_machine_error': '状态机异常',
+ 'itype.unauthorized_action': '未授权操作',
+
+ // ── EvaluationPanel ──
+ 'comp.eval.noResult': '暂无评估结果。',
+ 'comp.eval.startEval': '开始评估',
+ 'comp.eval.evaluating': '评估中...',
+ 'comp.eval.forceEval': '强制评估',
+ 'comp.eval.pendingCalls': '仍有 {n} 个 LLM 调用待处理。',
+ 'comp.eval.pendingWhenRan': '评估执行时仍有 {n} 个 LLM 调用待处理。',
+ 'comp.eval.collapseDetails': '收起详情',
+ 'comp.eval.viewDetails': '查看详情',
+ 'comp.eval.dimensions': '评估维度',
+ 'comp.eval.findings': '发现',
+ 'comp.eval.noFindings': '无发现。',
+ 'comp.eval.rootCausePrefix': '根因:',
+ 'comp.eval.summary.pass': '该对话已完成,未发现确定性的质量问题。',
+ 'comp.eval.summary.warn': '该对话可用但需要复核:{cause}。',
+ 'comp.eval.summary.fail': '评估失败,主要原因:{cause}。',
+ 'comp.eval.action.none': '请复核告警和佐证信息。',
+ 'comp.eval.action.noActionRequired': '无需额外操作。',
+ 'comp.eval.action.no_final_answer': '请检查最后一次 LLM 调用和服务端响应解析。',
+ 'comp.eval.action.interrupted_main_call': '请检查中断证据,修复运行时稳定性后重试会话。',
+ 'comp.eval.action.agent_crash': '请检查 agent 健康状态和崩溃诊断后再重试。',
+ 'comp.eval.action.runtime_error': '请检查模型服务错误、网络稳定性和重试行为。',
+ 'comp.eval.action.tool_failure': '请检查失败的工具调用和工具响应解析。',
+ 'comp.eval.action.safety_risk': '请复核安全相关发现后再运行 agent。',
+ 'comp.eval.action.loop_detected': '请检查重复调用并收紧停止条件。',
+ 'comp.eval.action.excessive_cost': '请审查提示词、工具输出和 Token 节省机会。',
+ 'comp.eval.action.partial_snapshot': '请等待待处理调用完成,或保持强制评估标记。',
+ 'comp.eval.cause.none': '未发现明确根因',
+ 'comp.eval.cause.no_final_answer': '未产生最终回答',
+ 'comp.eval.cause.interrupted_main_call': '主调用被中断',
+ 'comp.eval.cause.agent_crash': 'Agent 崩溃',
+ 'comp.eval.cause.runtime_error': '运行时错误',
+ 'comp.eval.cause.tool_failure': '工具调用失败',
+ 'comp.eval.cause.safety_risk': '安全风险',
+ 'comp.eval.cause.loop_detected': '疑似循环调用',
+ 'comp.eval.cause.excessive_cost': '成本过高',
+ 'comp.eval.cause.partial_snapshot': '快照不完整',
+ 'comp.eval.dim.completion': '完成度',
+ 'comp.eval.dim.runtime_health': '运行时健康',
+ 'comp.eval.dim.tool_use': '工具使用',
+ 'comp.eval.dim.efficiency': '效率',
+ 'comp.eval.dim.safety': '安全',
+ 'comp.eval.finding.no_final_answer': '无最终回答',
+ 'comp.eval.finding.interrupted_main_call': '主调用被中断',
+ 'comp.eval.finding.partial_snapshot': '快照不完整',
+ 'comp.eval.finding.tool_failure': '工具失败',
+ 'comp.eval.finding.loop_detected': '疑似循环',
+ 'comp.eval.finding.llm_error': 'LLM 错误',
+ 'comp.eval.finding.sse_truncated': 'SSE 截断',
+ 'comp.eval.finding.network_timeout': '网络超时',
+ 'comp.eval.finding.service_unavailable': '服务不可用',
+ 'comp.eval.finding.agent_crash': 'Agent 崩溃',
+
+ // ── OptimizationPage / OptimizationSettings ──
+ 'opt.error.requestFailedStatus': '请求失败 ({status})',
+ 'opt.loading.analyzingTag': '分析中',
+ 'opt.loading.calculating': '正在计算,请稍候...',
+ 'opt.error.analysisFailedHint': '分析失败 —— 可稍后点击「重新分析」重试。',
+ 'opt.dim.notAnalyzedHint': '该维度尚未分析 —— 点击右上角「开始分析」运行。',
+ 'opt.summary.generating': '正在生成轨迹摘要…',
+ 'opt.summary.title': '轨迹摘要',
+ 'opt.summary.goalLabel': '目标',
+ 'opt.summary.processLabel': '过程',
+ 'opt.summary.outcomeLabel': '结果',
+ 'opt.copy.promptTitle': '复制优化提示词',
+ 'opt.failure.toolErrorLabel': '工具错误',
+ 'opt.failure.reasoningErrorLabel': '推理错误',
+ 'opt.failure.timeoutLabel': '超时',
+ 'opt.failure.invalidUsageLabel': '无效调用',
+ 'opt.accuracy.failureRecovery': '恢复: {text}',
+ 'opt.accuracy.table.symptom': '现象',
+ 'opt.accuracy.table.defectType': '缺陷类型',
+ 'opt.accuracy.table.rootCause': '归因对象',
+ 'opt.accuracy.table.fixLocus': '修复落点',
+ 'opt.accuracy.table.confidence': '置信度',
+ 'opt.accuracy.table.prompt': '优化提示词',
+ 'opt.accuracy.recoveredBadge': '已恢复 · 优化线索',
+ 'opt.accuracy.notOptimizable': '不可优化',
+ 'opt.accuracy.detail.evidence': '证据',
+ 'opt.accuracy.detail.verify': '验证',
+ 'opt.accuracy.detail.fix': '修复',
+ 'opt.accuracy.fixDiverges': '锅在 {primary},改在 {fixLocus}',
+ 'opt.accuracy.fixLocusNone': '无',
+ 'opt.accuracy.fixSame': '与根因主因一致',
+ 'opt.accuracy.sectionTitle': '准确性剖析',
+ 'opt.accuracy.issueCountTag': '{count} 个问题',
+ 'opt.accuracy.noIssues': '✓ 未检测到影响最终产出准确性的问题。',
+ 'opt.accuracy.issueTableTitle': '失败清单 · 点击任意行展开根因与修复',
+ 'opt.accuracy.issueTableTitleFallback': '失败清单',
+ 'opt.accuracy.analyzeFailed': '准确性分析失败: {msg}',
+ 'opt.perf.state.error': '识别失败 —— LLM 调用出错,可稍后重试。',
+ 'opt.perf.state.loading': 'LLM 正在分析性能数据并选择优化策略…',
+ 'opt.perf.state.idle': '尚未分析 —— 点击「重新分析」运行 LLM 策略选择。',
+ 'opt.perf.state.noStrategies': '✓ LLM 分析完成,未发现适用的优化策略。',
+ 'opt.perf.table.symptom': '现象',
+ 'opt.perf.table.strategyType': '策略类型',
+ 'opt.perf.table.rootCause': '根因',
+ 'opt.perf.table.optimization': '优化策略',
+ 'opt.perf.sectionTitle': '性能剖析',
+ 'opt.perf.timeDistributionTitle': '时间分布',
+ 'opt.perf.slice.model': '模型推理',
+ 'opt.perf.slice.tools': '工具执行',
+ 'opt.perf.slice.idle': '用户空闲',
+ 'opt.perf.totalToolCalls': '{n} 次调用',
+ 'opt.perf.slowestCallsTitle': '最慢调用',
+ 'opt.perf.table.toolName': '工具',
+ 'opt.perf.table.duration': '耗时',
+ 'opt.perf.table.command': '命令',
+ 'opt.perf.noToolCalls': '该会话没有工具调用,耗时全部来自模型推理',
+ 'opt.cost.sectionTitle': '成本剖析',
+ 'opt.cost.tag.headroomMeasured': '{tokens} tok · Headroom 实测 -{pct}%',
+ 'opt.cost.tag.headroom': '{tokens} tok',
+ 'opt.cost.tag.noHeadroom': '{events} 事件 · {chars} 字符',
+ 'opt.cost.meta': '{chars} 字符 · {events} 事件 · {calls} 步 LLM 调用 · {source}',
+ 'opt.cost.tokSourceMeasuredUsage': 'token 实测(usage)',
+ 'opt.cost.tokSourceMeasuredSteps': 'token 实测 {used}/{total} 步',
+ 'opt.cost.tokSourceEstimated': 'token 估算',
+ 'opt.tab.accuracy.name': '准确性',
+ 'opt.tab.accuracy.label': '准确性剖析',
+ 'opt.tab.perf.name': '性能',
+ 'opt.tab.perf.label': '性能剖析',
+ 'opt.tab.cost.name': '成本',
+ 'opt.tab.cost.label': '成本剖析',
+ 'opt.tabs.ariaLabel': '维度剖析',
+ 'opt.session.backToList': '← 返回会话列表',
+ 'opt.session.headerTitle': '优化分析 · 会话',
+ 'opt.session.viewSourceTrajectory': '🔍 查看被分析轨迹 ↗',
+ 'opt.session.viewAnalysisTrajectory': '🤖 查看分析轨迹 (agentsight-opt) ↗',
+ 'opt.session.action.analyzing': '分析中...',
+ 'opt.session.action.reanalyze': '重新分析',
+ 'opt.session.action.start': '开始分析',
+ 'opt.session.loadingHistory': '加载历史分析结果...',
+ 'opt.session.noAnalysisYet': '该会话尚未进行优化分析',
+ 'opt.session.startHint': '点击「开始分析」并行运行准确性 / 性能 / 成本三维度剖析',
+ 'opt.llm.notConfiguredHint': 'LLM 尚未配置 —— 摘要 / 性能策略 / 成本浪费 / 准确性维度需要调用 LLM。',
+ 'opt.llm.goToSettings': '前往设置',
+ 'opt.summaryRow.issueCount': '{count} 个问题',
+ 'opt.summaryRow.accuracyLoading': '准确性分析中...',
+ 'opt.summaryRow.perf': '· {toolCount} 次工具调用 · {seconds}s',
+ 'opt.summaryRow.cost': '· {events} 事件',
+ 'opt.dim.summary': '摘要',
+ 'opt.dim.perf': '性能',
+ 'opt.dim.perfStrategy': '性能策略',
+ 'opt.dim.cost': '成本',
+ 'opt.dim.costWaste': '成本浪费',
+ 'opt.dim.accuracy': '准确性',
+ 'opt.entry.subtitle': '输入会话 ID,运行准确性 / 性能 / 成本三维度剖析',
+ 'opt.entry.sessionIdLabel': '会话 ID',
+ 'opt.entry.sessionIdPlaceholder': '粘贴会话 ID,回车开始分析',
+ 'opt.history.title': '历史分析记录',
+ 'opt.history.count': '共 {n} 条',
+ 'opt.history.limit': '(仅显示最近 {n} 条)',
+ 'opt.history.loadFailed': '加载历史记录失败: {msg}',
+ 'opt.history.loading': '加载历史分析记录...',
+ 'opt.history.empty': '还没有分析记录',
+ 'opt.history.col.sessionId': '会话 ID',
+ 'opt.history.col.dimensions': '已分析维度',
+ 'opt.history.col.firstAnalyzed': '首次分析',
+ 'opt.history.col.lastUpdated': '最近更新',
+ 'opt.history.paginationSummary': '{count} 条结果 · 第 {page}/{total} 页',
+ 'opt.llm.provider.dashscope': '阿里云 DashScope',
+ 'opt.llm.provider.openai': 'OpenAI',
+ 'opt.llm.provider.deepseek': 'DeepSeek',
+ 'opt.llm.provider.zhipu': '智谱 GLM',
+ 'opt.llm.provider.moonshot': 'Moonshot 月之暗面',
+ 'opt.llm.provider.custom': '自定义端点',
+ 'opt.llm.model.glm52Reasoning': 'GLM 5.2(推理)',
+ 'opt.llm.model.deepseekR1Reasoning': 'DeepSeek R1(推理)',
+ 'opt.llm.model.o3Reasoning': 'o3(推理)',
+ 'opt.llm.loadFailed': '加载配置失败: {msg}',
+ 'opt.llm.saveFailed': '保存失败: {msg}',
+ 'opt.llm.title': 'LLM 配置',
+ 'opt.llm.subtitle': '选择云服务厂商和模型,修改后立即生效,无需重启。用于优化分析(性能策略 / 成本浪费 / 准确性维度)。',
+ 'opt.llm.loading': '加载配置中...',
+ 'opt.llm.providerLabel': '云服务商',
+ 'opt.llm.apiKey.placeholder': '输入 API Key',
+ 'opt.llm.apiKey.hide': '隐藏',
+ 'opt.llm.apiKey.show': '显示',
+ 'opt.llm.apiKey.keepUnchanged': '留空则保持当前配置不变',
+ 'opt.llm.apiKey.hint': '在对应厂商控制台获取 API Key',
+ 'opt.llm.modelLabel': '模型',
+ 'opt.llm.model.customOption': '✏️ 自定义模型名...',
+ 'opt.llm.model.customPlaceholder': '输入模型 ID',
+ 'opt.llm.model.placeholder': '输入模型 ID,如 gpt-4o',
+ 'opt.llm.saving': '保存中...',
+ 'opt.llm.save': '保存配置',
+ 'opt.llm.saved': '✓ 配置已保存并生效',
+
+ // ── TokenFlameChart ──
+ 'flame.ctx.static': '静态区域',
+ 'flame.ctx.user': '用户提示词',
+ 'flame.ctx.assistant': '助手输出',
+ 'flame.ctx.tool': '工具输出',
+ 'flame.ctx.others': '其它',
+ 'flame.title': 'Token 火焰图',
+ 'flame.empty.noPerStepData': '该会话无逐步(per-step)成本数据 —— 可能是旧会话或轨迹解析失败。重新分析即可生成火焰图。',
+ 'flame.summary.totalTokens': '总 Token 数',
+ 'flame.summary.totalTokensSub': '输入 {input} · 输出 {output}',
+ 'flame.summary.peakContext': '峰值 Context',
+ 'flame.summary.peakContextSub': 'step {step} · {time} · 点击定位',
+ 'flame.chart.title': 'Context Window 组成 · 逐步重放',
+ 'flame.chart.subtitle': '点击柱体查看该步',
+ 'flame.legend.outputTokens': '输出 token',
+ 'flame.axis.contextLabel': '已发送 · context',
+ 'flame.waste.title': '浪费剖析',
+ 'flame.waste.error': '识别失败 —— LLM 调用出错,可稍后重试。',
+ 'flame.waste.loading': 'LLM 正在逐条判定候选是否值得优化…',
+ 'flame.waste.notAnalyzed': '尚未分析 —— 点击「重新分析」运行 LLM 浪费判定。',
+ 'flame.waste.noCandidates': '无可评估的浪费候选 —— 轨迹过短或采集内容不足,未做判定。',
+ 'flame.waste.noWasteFound': '✓ LLM 评估了 {count} 项候选,未发现值得优化的浪费。',
+ 'flame.waste.col.symptom': '现象',
+ 'flame.waste.col.type': '浪费类型',
+ 'flame.waste.col.optimization': '优化手段',
+ 'flame.waste.col.confidence': '置信度',
+ 'flame.waste.col.prompt': '优化提示词',
+ 'flame.confidence.high': '高',
+ 'flame.confidence.medium': '中',
+ 'flame.confidence.low': '低',
+ 'flame.detail.evidence': '证据',
+ 'flame.detail.experience': '经验 · 可直接贴进 Skill / 规范',
+ 'flame.detail.stepsNote': '共 {steps} 轮 · 单条轨迹证据,跨会话复现后可提升置信度',
+ 'flame.exp.applicability': '适用场景:',
+ 'flame.exp.pitfall': '错误做法:',
+ 'flame.exp.effectivePath': '正确做法:',
+ 'flame.exp.rule': '约定:',
+ 'flame.exp.badExample': '反例:',
+ 'flame.exp.goodExample': '正例:',
+ 'flame.exp.scope': '适用范围:',
+ 'flame.exp.symptom': '现象:',
+ 'flame.exp.optimization': '优化手段:',
+ 'flame.exp.evidence': '证据:',
+ 'flame.copyPromptTitle': '复制优化提示词',
+ 'flame.detail.stepTitle': '第 {id} 步',
+ 'flame.detail.currentOutput': '本轮输出',
+
+ // ── AgentSessionsPage ──
+ 'as.daysAgo': '{n} 天前',
+ 'as.timePreset.last24h': '最近 24h',
+ 'as.timePreset.last7d': '最近 7d',
+ 'as.timePreset.last30d': '最近 30d',
+ 'as.source.ebpf': 'eBPF 采集',
+ 'as.source.log': '日志采集',
+ 'as.fetchError': '获取会话列表失败',
+ 'as.totalSessions': '共 {n} 个会话',
+ 'as.autoRefresh': '自动刷新',
+ 'as.filter.allSources': '全部',
+ 'as.searchPlaceholder': '搜索会话 ID / 项目 / 消息内容...',
+ 'as.loadingSessions': '正在加载会话列表...',
+ 'as.noMatchingSessions': '没有匹配的会话',
+ 'as.noSessionsInRange': '当前时间范围内没有会话数据',
+ 'as.source': '来源',
+ 'as.project': '项目',
+ 'as.firstMessage': '首条消息',
+ 'as.lastMessage': '最新消息',
+ 'as.lastActive': '最近活跃',
+ 'as.copySessionId': '复制会话 ID',
+ 'as.subagentBadgeTitle': '该会话派生了 {n} 个子代理',
+ 'as.subagentCount': '{n} 子代理',
+ 'as.openInNewWindow': '点击在新窗口查看轨迹详情',
+ 'as.metricsTooltip': '消息/步数 {count} · Tokens {inTokens} / {outTokens}',
+ 'as.runOptimizationTitle': '对该会话运行优化分析',
+ 'as.analyze': '🔬 分析',
+ 'as.paginationSummary': '{total} 条结果 · 第 {cur}/{totalPages} 页',
+
+ // ── Small shared components / SettingsPage ──
+ 'comp.agentHealth.crashToast': '⚠️ Agent "{name}" (PID {pid}) 异常退出,影响了进行中的对话',
+ 'comp.agentHealth.hungToast': '⏳ Agent "{name}" (PID {pid}) 响应超时,可能卡顿',
+ 'comp.settings.title': '⚙️ 设置',
+ 'comp.settings.description': '管理 Dashboard 的全局配置。',
+
+ // ── RiskEnforcementPage ──
+ 'risk.error.requestFailed': '请求失败',
+ 'risk.mode.observe': '观察',
+ 'risk.mode.audit': '审计',
+ 'risk.mode.enforce': '拦截',
+ 'risk.bindingState.pending': '等待下发',
+ 'risk.bindingState.enforced': '执行中',
+ 'risk.bindingState.failed': '失败',
+ 'risk.bindingState.degraded': '降级',
+ 'risk.bindingState.detaching': '解除中',
+ 'risk.bindingState.detached': '已解除',
+ 'risk.effect.notify': '记录',
+ 'risk.effect.block': '拦截',
+ 'risk.effect.kill': '终止',
+ 'risk.operation.invalidPid': 'PID 必须是正整数',
+ 'risk.operation.createSucceeded': '策略已生效',
+ 'risk.operation.detachSucceeded': '策略已解除',
+ 'risk.detach.confirm': '确认解除这条风险拦截策略?',
+ 'risk.readiness.ready': '运行中',
+ 'risk.readiness.unavailable': '不可用',
+ 'risk.readiness.checking': '检测中',
+ 'risk.capability.loading': '正在读取执行器能力与就绪状态',
+ 'risk.capability.testDev': '测试/开发执行器:包含模拟拦截与策略交接',
+ 'risk.capability.prod': '生产执行器:仅支持观察与审计,不提供凭据拦截或策略交接',
+ 'risk.title.enforce': '风险拦截',
+ 'risk.title.observeAudit': '风险观察与审计',
+ 'risk.subtitle': '以敏感文件为数据源,通过 taint 传播关联网络外发,并按观察、审计、拦截渐进生效。',
+ 'risk.refresh.loading': '刷新中...',
+ 'risk.summary.enforcerStatus': '执行器状态',
+ 'risk.summary.backend': '执行后端',
+ 'risk.summary.activeBindings': '生效策略',
+ 'risk.summary.blockedViolations': '拦截违规',
+ 'risk.summary.auditedViolations': '审计违规',
+ 'risk.bindings.title': '策略绑定',
+ 'risk.bindings.header.agentPid': 'Agent / PID',
+ 'risk.bindings.header.sourcePath': '敏感文件',
+ 'risk.bindings.header.modeRevision': '模式 / 版本',
+ 'risk.bindings.header.state': '状态',
+ 'risk.bindings.header.actions': '操作',
+ 'risk.bindings.empty': '暂无策略绑定',
+ 'risk.bindings.sourcePathAria': '策略路径 {bindingId}',
+ 'risk.bindings.revisionLabel': '修订 #{revision}',
+ 'risk.bindings.detachAria': '解除 Agent {agentId}(PID {pid})对 {path} 的策略',
+ 'risk.bindings.detaching': '解除中...',
+ 'risk.bindings.detach': '解除策略',
+ 'risk.form.title': '下发敏感数据外发策略',
+ 'risk.form.sourcePathLabel': '敏感文件',
+ 'risk.form.modeLabel': '策略模式',
+ 'risk.form.scopeLabel': '目标范围',
+ 'risk.form.trustedEndpointLabel': '可信目标(可选)',
+ 'risk.form.sessionIdLabel': 'Session ID(可选)',
+ 'risk.form.scopeHelp': '仅全局可路由公网 IPv4;IPv6 与特殊用途地址不在当前采集范围内',
+ 'risk.form.mode.observeOption': '观察:建立证据链,不影响操作',
+ 'risk.form.mode.auditOption': '审计(推荐):规则判定并记录,不阻断',
+ 'risk.form.mode.enforceOption': '拦截:当前执行器不支持',
+ 'risk.form.mode.unreadyWarning': '当前执行器尚未就绪,无法下发策略。',
+ 'risk.form.mode.unsupportedWarning': '当前执行器未声明此模式的能力,无法下发策略。',
+ 'risk.form.bindingLimitWarning': '当前 ActPlane 后端最多支持 {maxActiveBindings} 条生效策略;请先解除现有策略后再下发。',
+ 'risk.form.submit.loading': '下发中...',
+ 'risk.form.submit': '下发策略',
+ 'risk.violations.title': '策略命中记录',
+ 'risk.violations.linkToAudit': '进入系统审计查看证据链',
+ 'risk.violations.header.time': '时间',
+ 'risk.violations.header.agentPid': 'Agent / PID',
+ 'risk.violations.header.operation': '操作',
+ 'risk.violations.header.target': '目标',
+ 'risk.violations.header.result': '结果',
+ 'risk.violations.header.reason': '原因',
+ 'risk.violations.empty': '暂无拦截记录',
+ 'risk.violations.result.blocked': '已拦截',
+ 'risk.violations.result.killed': '已终止',
+ 'risk.violations.result.logged': '已记录',
+
+ // ── ContainmentDialog / ContainmentLifecycleCard ──
+ 'cont.error.sourcePolicyUnavailable': '原始策略来源已不可用,无法安全生成拦截规则。',
+ 'cont.error.rootProcessStale': '目标进程已变化,请刷新后选择在线 Agent。',
+ 'cont.error.ambiguousCandidate': '目标进程身份不唯一,请刷新 Agent 状态后重试。',
+ 'cont.error.caseNotEligible': '当前案件状态不允许升级为拦截。',
+ 'cont.error.caseEligibilityChanged': '案件状态已变化,请关闭弹窗并刷新案件。',
+ 'cont.error.invalidDuration': '拦截时长不在服务端允许的范围内。',
+ 'cont.error.incompatibleAction': '该案件已有不同的拦截动作,请先查看现有动作。',
+ 'cont.error.actionInProgress': '拦截策略正在下发,请稍后刷新案件状态。',
+ 'cont.error.actionExpiring': '现有拦截策略正在解除,请稍后重试。',
+ 'cont.error.cleanupRequired': '旧策略仍需清理,请稍后重试或联系管理员。',
+ 'cont.error.enforcerUnavailable': '内核执行服务暂不可用,请稍后重试。',
+ 'cont.error.containmentDisabled': '当前环境未启用风险拦截能力。',
+ 'cont.error.recoveryFailed': '现有拦截动作恢复失败,请先处理该动作。',
+ 'cont.error.healthStoreUnavailable': '在线 Agent 状态暂不可用,请稍后重试。',
+ 'cont.error.retryable': '请求暂时失败,请稍后重试。',
+ 'cont.error.nonRetryable': '无法完成拦截,请刷新案件状态。',
+ 'cont.error.generic': '请求失败,请稍后重试。',
+ 'cont.existing.pending.title': '策略正在下发',
+ 'cont.existing.pending.message': '系统正在等待内核执行器确认,请稍后在案件详情中查看。',
+ 'cont.existing.activePersistent.title': '持续拦截已生效',
+ 'cont.existing.activePersistent.message': '该案件已有持续生效的内核策略,请在案件详情中查看或解除。',
+ 'cont.existing.activeTemporary.title': '临时拦截已生效',
+ 'cont.existing.activeTemporary.message': '该案件已有临时内核策略,请在案件详情中查看到期时间。',
+ 'cont.existing.expiring.title': '策略正在解除',
+ 'cont.existing.expiring.message': '内核策略正在清理,完成前不能重复下发。',
+ 'cont.existing.expired.title': '上次临时拦截已到期,可重新下发',
+ 'cont.existing.expired.message': '新动作仍会重新校验当前在线进程身份。',
+ 'cont.existing.failed.title': '上次拦截失败,可重新尝试',
+ 'cont.existing.failed.message': '请确认 Agent 在线且内核执行服务已恢复。',
+ 'cont.unavailable.loading': '正在读取执行器能力与就绪状态。',
+ 'cont.unavailable.notReady': '当前执行器尚未就绪,无法安全下发内核拦截。',
+ 'cont.unavailable.noCredentialEnforce': '当前执行器未显式声明凭据拦截与策略交接能力,无法下发内核拦截。',
+ 'cont.dialog.title.available': '确认升级为内核拦截',
+ 'cont.dialog.title.unavailable': '内核拦截不可用',
+ 'cont.dialog.subtitle': 'AgentSight 将从案件原始策略生成规则,Dashboard 不接收或展示策略 DSL。',
+ 'cont.dialog.closeAria': '关闭',
+ 'cont.plan.loading': '正在加载拦截方案...',
+ 'cont.plan.field.sourcePath': '敏感文件',
+ 'cont.plan.field.scope': '拦截范围',
+ 'cont.plan.field.effect': '执行效果',
+ 'cont.plan.field.originalAgent': '原始 Agent',
+ 'cont.plan.scope.untrustedIpv4': '不受信任的公网 IPv4 目标',
+ 'cont.plan.effect.actplaneDeny': 'ActPlane 内核拒绝(deny)',
+ 'cont.plan.original.unavailable': '原始进程不可用',
+ 'cont.target.valid': '进程身份有效:PID {pid}',
+ 'cont.target.selectLabel': '选择同一 Agent 的在线进程',
+ 'cont.target.placeholder': '请选择在线 Agent',
+ 'cont.target.recheckHint': '原始 PID 已失效;服务端会在下发前再次校验所选进程的启动时间与 Agent 身份。',
+ 'cont.target.noneAvailable': '原始进程身份已失效,且当前未发现执行器允许的同一 Agent 在线进程。',
+ 'cont.duration.legend': '拦截时长',
+ 'cont.duration.temporaryAria': '临时拦截 {minutes} 分钟',
+ 'cont.duration.temporaryTitle': '临时拦截 {minutes} 分钟',
+ 'cont.duration.temporaryDesc': '到期后由 AgentSight 自动解除。',
+ 'cont.duration.persistentAria': '持续拦截(需手动解除)',
+ 'cont.duration.persistentTitle': '持续拦截(需手动解除)',
+ 'cont.duration.persistentDesc': '仅在明确选择后启用,不会自动到期。',
+ 'cont.footer.close': '关闭',
+ 'cont.footer.cancel': '取消',
+ 'cont.footer.submit.loading': '正在下发...',
+ 'cont.footer.submit': '确认并下发',
+ 'cont.lifecycle.title': '风险拦截状态',
+ 'cont.lifecycle.sectionTitle': '风险拦截',
+ 'cont.lifecycle.loading': '正在加载拦截状态...',
+ 'cont.lifecycle.error': '拦截状态暂时不可用,请刷新后重试。',
+ 'cont.lifecycle.empty': '待升级:当前仅审计,不阻断系统行为。',
+ 'cont.lifecycle.upgrade.retry': '重新下发拦截',
+ 'cont.lifecycle.upgrade': '升级为拦截',
+ 'cont.lifecycle.field.targetProcess': '目标进程',
+ 'cont.lifecycle.field.binding': '策略绑定',
+ 'cont.lifecycle.field.expiresAt': '到期时间',
+ 'cont.lifecycle.field.remaining': '剩余时间',
+ 'cont.lifecycle.field.firstBlocked': '首次阻断',
+ 'cont.lifecycle.field.failureStage': '失败阶段',
+ 'cont.lifecycle.field.failureSummary': '失败说明',
+ 'cont.lifecycle.expires.persistent': '持续生效',
+ 'cont.lifecycle.remaining.persistent': '需手动解除',
+ 'cont.lifecycle.markResolved': '标记已处置',
+ 'cont.failureStage.attach': '策略挂载',
+ 'cont.failureStage.detach': '策略解除',
+ 'cont.failureStage.reconcile': '状态恢复',
+ 'cont.remaining.waitRefresh': '等待状态刷新',
+ 'cont.lifecycle.pending.label': '等待执行',
+ 'cont.lifecycle.pending.detail': '策略已提交,等待执行器确认',
+ 'cont.lifecycle.activeBlocked.label': '已遏制',
+ 'cont.lifecycle.activeBlocked.detail': '内核已确认阻断',
+ 'cont.lifecycle.activePending.label': '策略生效',
+ 'cont.lifecycle.activePending.detail': '等待首次内核阻断',
+ 'cont.lifecycle.expiring.label': '正在解除',
+ 'cont.lifecycle.expiring.detail': '执行器正在清理策略',
+ 'cont.lifecycle.expired.label': '已到期',
+ 'cont.lifecycle.expired.detail': '临时策略已解除',
+ 'cont.lifecycle.failed.label': '执行失败',
+ 'cont.lifecycle.failed.detail': '可在确认运行状态后重新下发',
+
+ // ── SystemAuditPage ──
+ 'audit.title': '系统审计',
+ 'audit.badge.localData': 'AgentSight 本地数据',
+ 'audit.description': '从 Agent 会话出发,关联工具调用、进程、文件、网络和 ActPlane 决策,形成可追溯证据链。',
+ 'audit.tab.overview': '审计总览',
+ 'audit.tab.sessions': '会话审计',
+ 'audit.tab.cases': '风险案件',
+ 'audit.tab.events': '事件检索',
+ 'audit.stats.totalEvents.label': '审计事件',
+ 'audit.stats.totalEvents.hint': '文件 / 标签 / 网络 / 判定',
+ 'audit.stats.sessions.label': '关联会话',
+ 'audit.stats.sessions.hint': '具备系统行为证据',
+ 'audit.stats.cases.label': '风险案件',
+ 'audit.stats.cases.hint': '规则关联后形成案件',
+ 'audit.stats.open.label': '待研判',
+ 'audit.stats.open.hint': '等待安全人员确认',
+ 'audit.stats.blocked.label': '确认拦截',
+ 'audit.stats.blocked.hint': '内核已返回拒绝结果',
+ 'audit.button.viewRawEvents': '查看原始事件',
+ 'audit.error.loadFailed': '系统审计数据加载失败',
+ 'audit.status.open': '待研判',
+ 'audit.status.confirmed': '已确认',
+ 'audit.status.falsePositive': '误报',
+ 'audit.status.acceptedRisk': '已接受',
+ 'audit.status.resolved': '已处置',
+ 'audit.case.blocked': '内核已拦截',
+ 'audit.case.notBlocked': '未确认阻断',
+ 'audit.evidence.loading': '正在加载证据链...',
+ 'audit.evidence.selectCasePlaceholder': '请选择一个风险案件',
+ 'audit.case.summaryTitle': '风险结论',
+ 'audit.case.summaryLine': '{decision} · 风险分 {riskScore} · 策略修订 #{policyRevision}',
+ 'audit.button.confirmRisk': '确认风险',
+ 'audit.button.markFalsePositive': '标记误报',
+ 'audit.button.acceptRisk': '接受风险',
+ 'audit.button.markResolved': '标记已处置',
+ 'audit.evidence.fullChainTitle': '完整证据链',
+ 'audit.event.fileAction': '文件读取',
+ 'audit.event.taintTransition': '标签传递',
+ 'audit.event.networkAction': '网络连接',
+ 'audit.event.policyDecision': '策略判定',
+ 'audit.event.enforcementState': '执行状态',
+ 'audit.event.fallback': '系统事件',
+ 'audit.evidence.fileFallback': '敏感文件访问',
+ 'audit.evidence.networkFallback': '未知网络目标',
+ 'audit.evidence.policyFallback': '策略完成判定',
+ 'audit.evidence.systemEventFallback': '系统事件',
+ 'audit.cases.empty': '暂无风险案件',
+ 'audit.case.noSession': '无会话',
+ 'audit.cases.description': '选择案件查看完整、按时序排列的原始证据。',
+ 'audit.sessions.systemEvents': '系统事件',
+ 'audit.sessions.timeRange': '时间范围',
+ 'audit.sessions.entryPoint': '入口',
+ 'audit.sessions.viewTimeline': '查看时间线',
+ 'audit.events.result': '结果',
+
+ // ── SkillMetricsPage ──
+ 'skill.error.loadFailed': '获取技能指标失败',
+ 'skill.concept.note': '本页面统计单位为一次 LLM 调用(对应一条 GenAI 事件记录)。',
+ 'skill.summary.eventCount': '分析调用数',
+ 'skill.summary.discoveredSkills': '已发现技能',
+ 'skill.summary.totalLoads': '总加载次数',
+ 'skill.summary.usageRatio': '技能使用率',
+ 'skill.section.loads': '技能加载次数',
+ 'skill.section.skillsPerCall': '单次调用技能数分布',
+ 'skill.distribution.axisLabel': '单次调用技能数',
+ 'skill.distribution.min': '最小值',
+ 'skill.distribution.max': '最大值',
+ 'skill.distribution.mean': '均值',
+ 'skill.section.hotnessRanking': '技能热度排行',
+ 'skill.hotness.granularityLabel': '趋势粒度:',
+ 'skill.hotness.day': '按天',
+ 'skill.hotness.week': '按周',
+ 'skill.hotness.rank': '排名',
+ 'skill.hotness.skillName': '技能',
+ 'skill.hotness.totalLoads': '总加载次数',
+ 'skill.hotness.trend': '趋势',
},
};
+// ─── Interruption type labels ─────────────────────────────────────────────────
+// Single source of truth for interruption_type → label mapping, shared by the
+// health page filter, interruption panels and evaluation findings.
+
+const INTERRUPTION_TYPE_KEY: Record = {
+ llm_error: 'itype.llm_error',
+ sse_truncated: 'itype.sse_truncated',
+ context_overflow: 'itype.context_overflow',
+ agent_crash: 'itype.agent_crash',
+ token_limit: 'itype.token_limit',
+ rate_limit: 'itype.rate_limit',
+ auth_error: 'itype.auth_error',
+ network_timeout: 'itype.network_timeout',
+ service_unavailable: 'itype.service_unavailable',
+ safety_filter: 'itype.safety_filter',
+ retry_storm: 'itype.retry_storm',
+ dead_loop: 'itype.dead_loop',
+ tool_failure: 'itype.tool_failure',
+ empty_response: 'itype.empty_response',
+ resource_exhaustion: 'itype.resource_exhaustion',
+ slow_response: 'itype.slow_response',
+ state_machine_error: 'itype.state_machine_error',
+ unauthorized_action: 'itype.unauthorized_action',
+};
+
+/** All known interruption_type identifiers (filter dropdowns etc.). */
+export const INTERRUPTION_TYPES: readonly string[] = Object.keys(INTERRUPTION_TYPE_KEY);
+
+/** Returns the message key for an interruption_type, if known. */
+export function interruptionTypeKey(type: string): MessageKey | undefined {
+ return INTERRUPTION_TYPE_KEY[type];
+}
+
+// ─── Context & Provider ───────────────────────────────────────────────────────
+
interface I18nContextValue {
locale: Locale;
setLocale: (locale: Locale) => void;
- t: (key: MessageKey) => string;
+ t: (key: MessageKey, params?: Record) => string;
}
const I18nContext = createContext(null);
@@ -133,6 +2067,11 @@ function syncDocumentMetadata(locale: Locale): void {
}
}
+/** Returns the BCP-47 tag for use with Intl APIs (e.g. toLocaleString). */
+export function localeTag(locale: Locale): string {
+ return locale;
+}
+
export const I18nProvider: React.FC = ({ children }) => {
const [locale, setLocaleState] = useState(resolveInitialLocale);
@@ -151,7 +2090,15 @@ export const I18nProvider: React.FC = ({ children }) =>
}, []);
const t = useCallback(
- (key: MessageKey) => messages[locale][key],
+ (key: MessageKey, params?: Record) => {
+ let msg = messages[locale][key];
+ if (params) {
+ for (const [k, v] of Object.entries(params)) {
+ msg = msg.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
+ }
+ }
+ return msg;
+ },
[locale],
);
@@ -175,6 +2122,14 @@ export function useI18n(): I18nContextValue {
return context;
}
+/** Convenience hook: returns the locale tag for Intl APIs. */
+export function useLocaleTag(): string {
+ const { locale } = useI18n();
+ return localeTag(locale);
+}
+
+// ─── LanguageSwitcher ─────────────────────────────────────────────────────────
+
interface LanguageSwitcherProps {
id: string;
className?: string;
diff --git a/src/agentsight/dashboard/src/pages/AgentHealthPage.tsx b/src/agentsight/dashboard/src/pages/AgentHealthPage.tsx
index c4ec00d0fe..f5b7249427 100644
--- a/src/agentsight/dashboard/src/pages/AgentHealthPage.tsx
+++ b/src/agentsight/dashboard/src/pages/AgentHealthPage.tsx
@@ -5,10 +5,12 @@ import {
restartAgentHealth,
fetchInterruptions,
resolveInterruption,
- INTERRUPTION_TYPE_CN,
} from '../utils/apiClient';
import type { InterruptionRecord, InterruptionSeverity } from '../utils/apiClient';
import type { AgentHealthStatus } from '../types';
+import { useI18n, useLocaleTag, INTERRUPTION_TYPES, interruptionTypeKey } from '../i18n';
+import type { MessageKey } from '../i18n';
+import { formatNs } from '../utils/datetime';
// ─── Agent status section ─────────────────────────────────────────────────────
@@ -22,34 +24,34 @@ const STATUS_COLORS: Record = {
offline: 'bg-gray-500',
};
-/** Status display label */
-const STATUS_LABELS: Record = {
- healthy: '正常',
- unhealthy: '端口无响应',
- hung: '响应卡住',
- unknown: '待检测',
- no_port: '客户端进程',
- offline: '异常退出',
+/** Status display label key */
+const STATUS_LABEL_KEY: Record = {
+ healthy: 'ah.status.healthy',
+ unhealthy: 'ah.status.unhealthy',
+ hung: 'ah.status.hung',
+ unknown: 'ah.status.unknown',
+ no_port: 'ah.status.noPort',
+ offline: 'ah.status.offline',
};
-/** Status tooltip / 描述,帮助用户理解状态含义 */
-const STATUS_TOOLTIPS: Record = {
- healthy: '服务监听端口且 HTTP 探活成功',
- unhealthy: '端口不接受连接,可能需要重启',
- hung: '端口可连但 HTTP 探活超时,进程可能卡死',
- unknown: '首轮健康检查未完成',
- no_port: 'TUI / 子进程,本身不提供服务端口(正常)',
- offline: '进程异常退出,影响了进行中的 LLM 对话,5 分钟后自动移除',
+/** Status tooltip key */
+const STATUS_TOOLTIP_KEY: Record = {
+ healthy: 'ah.tooltip.healthy',
+ unhealthy: 'ah.tooltip.unhealthy',
+ hung: 'ah.tooltip.hung',
+ unknown: 'ah.tooltip.unknown',
+ no_port: 'ah.tooltip.noPort',
+ offline: 'ah.tooltip.offline',
};
-/** Format relative time in Chinese */
-function relativeTime(timestampMs: number): string {
+/** Format relative time */
+function relativeTime(timestampMs: number, t: (key: MessageKey, params?: Record) => string): string {
if (timestampMs === 0) return '—';
const diff = Math.floor((Date.now() - timestampMs) / 1000);
- if (diff < 5) return '刚刚';
- if (diff < 60) return `${diff} 秒前`;
- if (diff < 3600) return `${Math.floor(diff / 60)} 分钟前`;
- return `${Math.floor(diff / 3600)} 小时前`;
+ if (diff < 5) return t('common.justNow');
+ if (diff < 60) return t('common.secondsAgo', { n: diff });
+ if (diff < 3600) return t('common.minutesAgo', { n: Math.floor(diff / 60) });
+ return t('common.hoursAgo', { n: Math.floor(diff / 3600) });
}
interface Toast {
@@ -64,31 +66,36 @@ const AgentCard: React.FC<{
onRestart: (pid: number) => void;
restarting: boolean;
}> = ({ agent, related, onDelete, onRestart, restarting }) => {
+ const { t } = useI18n();
const [showRelated, setShowRelated] = useState(false);
- // 区分:真 Gateway = 本身在监听端口的服务进程(如 OpenClaw Gateway)
- // 升格 Gateway = 被升格为主卡的单进程 agent(如 Hermes Python CLI)—
- // 这种不该贴“Gateway”标签,他们业务上没有 gateway 概念。
+ // Real Gateway = a service process that listens on a port itself (e.g. OpenClaw Gateway).
+ // Promoted Gateway = a single-process agent promoted to a main card (e.g. Hermes Python CLI) —
+ // these should not carry a "Gateway" label since they have no gateway concept.
const hasPorts = (agent.ports?.length ?? 0) > 0;
const isRealGateway = agent.role === 'gateway' && hasPorts;
const isPromotedGateway = agent.role === 'gateway' && !hasPorts;
- // 升格 Gateway + status=no_port 用“运行中”绿色,避免原 no_port 的
- // “客户端进程”灰色语义与主卡身份冲突。
+ // Promoted Gateway + status=no_port uses the green "Running" label to avoid
+ // the gray "client process" semantics conflicting with main-card identity.
const useRunningStatus = isPromotedGateway && agent.status === 'no_port';
const dotColor = useRunningStatus
? 'bg-green-500'
: STATUS_COLORS[agent.status] || 'bg-gray-400';
- const label = useRunningStatus ? '运行中' : STATUS_LABELS[agent.status] || agent.status;
+ const labelKey = STATUS_LABEL_KEY[agent.status];
+ const label = useRunningStatus
+ ? t('ah.running')
+ : labelKey ? t(labelKey) : agent.status;
+ const tooltipKey = STATUS_TOOLTIP_KEY[agent.status];
const tooltip = useRunningStatus
- ? '单进程 agent,本身不提供服务端口,运行正常'
- : STATUS_TOOLTIPS[agent.status] || '';
+ ? t('ah.tooltip.running')
+ : tooltipKey ? t(tooltipKey) : '';
const isOffline = agent.status === 'offline';
const isHung = agent.status === 'hung';
const isUnhealthy = agent.status === 'unhealthy';
const canRestart = isHung && !!agent.restart_cmd?.length;
- // 计算 offline 项距离自动移除还有多久(5 分钟 TTL)
+ // Time until offline entries are auto-removed (5-minute TTL)
const OFFLINE_TTL_MS = 5 * 60 * 1000;
const offlineRemainSec =
isOffline && agent.offline_since
@@ -115,6 +122,12 @@ const AgentCard: React.FC<{
? 'text-red-500 font-semibold'
: 'text-gray-400';
+ const remainText = offlineRemainSec !== null && offlineRemainSec > 0
+ ? (offlineRemainSec >= 60
+ ? `${Math.ceil(offlineRemainSec / 60)} ${t('common.min')}`
+ : `${offlineRemainSec} ${t('common.sec')}`)
+ : null;
+
return (
@@ -122,17 +135,17 @@ const AgentCard: React.FC<{
{agent.agent_name}
{isRealGateway && (
- Gateway
+ {t('ah.gateway')}
)}
{agent.role === 'client' && (
- 客户端
+ {t('ah.client')}
)}
{agent.role === 'worker' && (
- Worker
+ {t('ah.worker')}
)}
{label}
@@ -146,7 +159,7 @@ const AgentCard: React.FC<{
{agent.latency_ms !== null && agent.status === 'healthy' && (
{agent.latency_ms}ms
)}
- {relativeTime(agent.last_check_time)}
+ {relativeTime(agent.last_check_time, t)}
{agent.error_message && !isOffline && (
- {offlineRemainSec > 0
- ? `${
- offlineRemainSec >= 60
- ? Math.ceil(offlineRemainSec / 60) + ' 分钟'
- : offlineRemainSec + ' 秒'
- }后自动移除`
- : '即将移除'}
+ {remainText
+ ? t('ah.untilAutoRemoval', { time: remainText })
+ : t('ah.removingSoon')}
)}
@@ -175,7 +184,7 @@ const AgentCard: React.FC<{
onClick={() => onDelete(agent.pid)}
className="text-xs text-gray-400 hover:text-gray-600 underline"
>
- 立即移除
+ {t('ah.removeNow')}
)}
{canRestart && (
@@ -184,7 +193,7 @@ const AgentCard: React.FC<{
disabled={restarting}
className="text-xs text-orange-500 hover:text-orange-700 underline disabled:opacity-50 disabled:cursor-not-allowed"
>
- {restarting ? '重启中...' : '重启进程'}
+ {restarting ? t('ah.restarting') : t('ah.restartProcess')}
)}
@@ -196,7 +205,7 @@ const AgentCard: React.FC<{
className="text-[11px] text-gray-500 hover:text-gray-700 flex items-center gap-1"
>
▶
- 关联进程 ({related.length})
+ {t('ah.relatedProcesses', { n: related.length })}
{showRelated && (
@@ -204,7 +213,7 @@ const AgentCard: React.FC<{
- {ca.role === 'worker' ? 'Worker' : '客户端'}
+ {ca.role === 'worker' ? t('ah.worker') : t('ah.client')}
PID {ca.pid}
@@ -218,6 +227,7 @@ const AgentCard: React.FC<{
};
const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ addToast }) => {
+ const { t } = useI18n();
const [agents, setAgents] = useState ([]);
const [clientAgents, setClientAgents] = useState([]);
const [showOrphans, setShowOrphans] = useState(false);
@@ -229,7 +239,7 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
const refresh = useCallback(async () => {
try {
- // 一次拉全部(包含 client/worker),后续按 parent_pid 分组挂到各自主卡下面
+ // Fetch all at once (including client/worker), then group by parent_pid under each main card
const data = await fetchAgentHealth({ includeClients: true });
const agentRows = Array.isArray(data?.agents) ? data.agents : [];
setAgents(agentRows.filter(a => a.role === 'gateway'));
@@ -239,19 +249,19 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
hasDataRef.current = true;
} catch (e: any) {
if (!hasDataRef.current) {
- setError(e.message || '请求失败');
+ setError(e.message || t('ah.requestFailed'));
}
} finally {
setLoading(false);
}
- }, []);
+ }, [t]);
const handleDelete = async (pid: number) => {
try {
await deleteAgentHealth(pid);
setAgents(prev => prev.filter(a => a.pid !== pid));
} catch (e: any) {
- addToast(`删除失败: ${e.message}`);
+ addToast(t('ah.deleteFailed', { msg: e.message }));
}
};
@@ -259,11 +269,11 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
setRestartingPids(prev => new Set(prev).add(pid));
try {
const result = await restartAgentHealth(pid);
- addToast(`✅ 重启成功,新 PID: ${result.new_pid},等待进程上线...`);
- // 立即从本地列表删除旧条目,不等下次扫描(新 PID 会在 30s 内自动出现)
+ addToast(t('ah.restartSucceeded', { pid: result.new_pid }));
+ // Remove the old entry from the local list immediately (the new PID appears within 30s)
setAgents(prev => prev.filter(a => a.pid !== pid));
} catch (e: any) {
- addToast(`重启失败: ${e.message}`);
+ addToast(t('ah.restartFailed', { msg: e.message }));
} finally {
setRestartingPids(prev => {
const next = new Set(prev);
@@ -279,7 +289,7 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
return () => clearInterval(timer);
}, [refresh]);
- // 排序:hung/unhealthy 首位(真有问题),正常中间,offline 最后(不抢眼)
+ // Sort: hung/unhealthy first (real problems), healthy in the middle, offline last (less prominent)
const sorted = [...agents].sort((a, b) => {
const order: Record = {
hung: 0,
@@ -298,8 +308,8 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
const totalCount = agents.length;
const gatewayPids = new Set(sorted.map(a => a.pid));
- // 孤儿关联进程:Worker 但父进程不是任何主卡(不应出现,兜底)。
- // 过滤 status=offline 的进程——它们 5 分钟后会被 TTL 自动清理。
+ // Orphaned related processes: workers whose parent is not any main card (should not happen, fallback).
+ // Filter out status=offline processes — they are TTL-cleaned after 5 minutes.
const orphans = clientAgents.filter(
c =>
c.status !== 'offline' &&
@@ -312,38 +322,38 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
- Agent 看板
+ {t('ah.agentDashboard')}
{offlineCount > 0 && (
- {offlineCount} 崩溃
+ {t('ah.crashed', { n: offlineCount })}
)}
{hungCount > 0 && (
- {hungCount} 卡顿
+ {t('ah.hungCount', { n: hungCount })}
)}
{totalCount > 0 && (
- {healthyCount}/{totalCount} 正常
+ {t('ah.healthyRatio', { healthy: healthyCount, total: totalCount })}
)}
{lastScan > 0 && (
- 上次扫描: {relativeTime(lastScan)}
+ {t('ah.lastScan', { time: relativeTime(lastScan, t) })}
)}
{loading ? (
- 加载中...
+ {t('common.loading')}
) : error ? (
{error}
) : sorted.length === 0 ? (
- 暂无已发现的 Agent
+ {t('ah.noAgents')}
) : (
@@ -351,8 +361,8 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
c.parent_pid === agent.pid)}
onDelete={handleDelete}
onRestart={handleRestart}
@@ -369,7 +379,7 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
className="text-xs text-gray-500 hover:text-gray-700 flex items-center gap-1"
>
▶
- 孤儿关联进程 ({orphans.length})
+ {t('ah.orphanedProcesses', { n: orphans.length })}
{showOrphans && (
@@ -378,7 +388,7 @@ const AgentStatusSection: React.FC<{ addToast: (msg: string) => void }> = ({ add
{ca.agent_name}
- {ca.role === 'worker' ? 'Worker' : '客户端'}
+ {ca.role === 'worker' ? t('ah.worker') : t('ah.client')}
PID {ca.pid}
@@ -400,25 +410,21 @@ const SEVERITY_DOT: Record = {
low: 'bg-blue-400',
};
-const SEVERITY_LABELS: Record = {
- critical: '致命',
- high: '严重',
- medium: '中等',
- low: '轻微',
+const SEVERITY_LABEL_KEY: Record = {
+ critical: 'common.critical',
+ high: 'common.high',
+ medium: 'common.medium',
+ low: 'common.low',
};
-const TIME_RANGES: { label: string; hours: number }[] = [
- { label: '最近 1 小时', hours: 1 },
- { label: '最近 24 小时', hours: 24 },
- { label: '最近 7 天', hours: 24 * 7 },
+const TIME_RANGE_KEYS: { labelKey: MessageKey; hours: number }[] = [
+ { labelKey: 'ah.last1Hour', hours: 1 },
+ { labelKey: 'ah.last24Hours', hours: 24 },
+ { labelKey: 'ah.last7Days', hours: 24 * 7 },
];
-function formatNs(ns: number): string {
- return new Date(ns / 1_000_000).toLocaleString();
-}
-
-function parseDetail(raw: string | null): React.ReactNode {
- if (!raw) return 无详情;
+function parseDetail(raw: string | null, t: (key: MessageKey) => string): React.ReactNode {
+ if (!raw) return {t('ah.noDetails')};
try {
const obj = JSON.parse(raw);
return (
@@ -436,13 +442,14 @@ const IDWithCopy: React.FC<{ value: string | null; addToast: (msg: string) => vo
value,
addToast,
}) => {
+ const { t } = useI18n();
if (!value) return —;
const short = value.length > 8 ? `${value.slice(0, 8)}…` : value;
const copy = async (e: React.MouseEvent) => {
e.stopPropagation();
let ok = false;
try {
- // 优先 clipboard API(HTTPS / localhost)
+ // Prefer the clipboard API (HTTPS / localhost)
if (window.isSecureContext && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(value);
ok = true;
@@ -451,7 +458,7 @@ const IDWithCopy: React.FC<{ value: string | null; addToast: (msg: string) => vo
// fall through
}
if (!ok) {
- // 降级:临时 textarea + execCommand,HTTP 环境也能用
+ // Fallback: temporary textarea + execCommand, works over HTTP too
try {
const ta = document.createElement('textarea');
ta.value = value;
@@ -467,7 +474,7 @@ const IDWithCopy: React.FC<{ value: string | null; addToast: (msg: string) => vo
ok = false;
}
}
- addToast(ok ? `已复制: ${value}` : `复制失败: ${value}`);
+ addToast(ok ? t('ah.copiedValue', { value }) : t('ah.copyFailedValue', { value }));
};
return (
@@ -475,9 +482,9 @@ const IDWithCopy: React.FC<{ value: string | null; addToast: (msg: string) => vo
| |