From 4f2d279865ae9767163b8ceff32279ac14398214 Mon Sep 17 00:00:00 2001 From: attacker Date: Sun, 12 Jul 2026 12:17:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor(i18n):=20U31=20AIOps=20Agent=20CLI=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20i18n=20=E4=BF=AE=E5=A4=8D=20=E2=80=94=20Q7?= =?UTF-8?q?/Q35=20=E6=8B=86=E5=88=86=E5=90=8E=E4=B8=A2=E5=A4=B1=E7=9A=84?= =?UTF-8?q?=20i18n=20=E9=9B=86=E6=88=90=E5=85=A8=E9=83=A8=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 6 个文件迁移 (AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts): - 新增 aiops.cli.* 命名空间 100 翻译键 (10 子分类) - SAFETY_MODES/TIPS_HOST/TIPS_KUBERNETES 常量改为 getSafetyModes(t)/getTipsHost(t)/getTipsKubernetes(t) 工厂函数 - 全部 UI 硬编码中文清零 (66→0) - zh/en 7381→7481 +100 键对齐 - tsc -b=0, npm build✓ (18.21s), ESLint=0/53/0, go build=0, 17/17 测试通过 --- ROADMAP.md | 31 ++++ frontend/src/components/aiops/AgentCLI.tsx | 45 +++--- .../components/aiops/AgentCLIEmptyState.tsx | 19 +-- .../src/components/aiops/AgentCLIHistory.tsx | 45 +++--- .../src/components/aiops/AgentCLIToolbar.tsx | 63 ++++---- .../components/aiops/agent-cli-constants.ts | 110 ++++++++------ .../aiops/agent-cli/ChatInputArea.tsx | 8 +- frontend/src/locales/en-US.json | 134 +++++++++++++++++- frontend/src/locales/zh-CN.json | 134 +++++++++++++++++- 9 files changed, 461 insertions(+), 128 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 4823b1d..2fd4f82 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -519,6 +519,7 @@ services/api.ts (1324L) | U26 | Kubernetes 模块 phantom key 大规模清理(32 个 .tsx/.ts 文件,171 个中文 phantom key → 8 个残留(均为 ANSI 转义码/JSX 表达式误匹配),163 个成功替换为 k8s.* 语义化键;新增 19 个子命名空间(nsResource/nsOverview/deploymentWorkload/deploymentView/workloadView/controlBar/clusterOverview/nodes/podLogs/podExec/pvc/configMap/secret/service/ingress/nsMgmt/editContainers/capacityPage/podFile)+ 扩展 8 个已有子命名空间;zh/en 6990→7185 +195 键对齐,k8s 命名空间 65 子命名空间/940 键,tsc -b=0,npm build✓ (17.59s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | | U27 | 多模块 phantom key 批量清理(Cloud/Deploy/Agent/Monitor/System 5 模块 25 个文件,180 个中文 phantom key → 51 个残留,129 个成功替换为语义化键;新增 12 个子命名空间(cloud.platform/deploy.buildNodes/deploy.rbac.policy/deploy.stage/deploy.target/deploy.userGroup/agent.alerts/agent.monitor/agent.alertHelper/agent.ruleDialog/monitor.alertRulesTab/system.ldapMgmt 等);硬编码 JSX 中文(刷新/添加/启用/禁用/Master内置/执行器/标签等) → i18n.t() 调用;zh/en 7185→7294 +109 键对齐,tsc -b=0,npm build✓ (16.31s),ESLint=0/0/0,go build=0,17/17 测试通过) | P3 | **done** | | U30 | i18n 批量转换损坏调用修复(8 处损坏的 i18n.t() 调用修复 — DeploymentEditContainers.tsx:85 toast description 损坏 / DeploymentEditStorage.tsx:175,202 !manifest 空状态+数据卷副标题损坏 / AgentRuleDialog.tsx:111,157,183,195 DialogDescription+操作符/端口/持续时间 Label 损坏 / LogFieldSidebar.tsx:68 搜索 placeholder 损坏。损坏原因: 早期批量正则 i18n 转换将模板表达式/JSX 碎片错误包裹进 i18n.t() 调用。新增 6 个语义化键(agent.ruleDialog.description/operator/port/duration + kubernetes.storage.volumeDesc + monitor.lbl.searchFields),zh/en 7375→7381 +6 键对齐,tsc -b=0,npm build✓ (17.99s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | +| U31 | AIOps Agent CLI 组件 i18n 修复(AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts 6 文件 — Q7/Q35 组件拆分后丢失的 i18n 集成全部修复,新增 `aiops.cli.*` 命名空间 100 翻译键(8 子分类: toolbar/safety/tips/error/info/viewMode/placeholder/history/empty/input),SAFETY_MODES+TIPS_HOST+TIPS_KUBERNETES 常量数组改为 `getSafetyModes(t)`/`getTipsHost(t)`/`getTipsKubernetes(t)` 接收 TFunction 参数的工厂函数,全部 UI 硬编码中文清零,zh/en 7381→7481 +100 键对齐,tsc -b=0,npm build✓ (18.21s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | --- @@ -1528,3 +1529,33 @@ pending → in_progress → done / blocked - [x] `go build` 零错误 - [x] Docker 部署 healthy +--- + +## U31: AIOps Agent CLI 组件 i18n 修复 [done] + +**问题**: AIOps Agent CLI 组件群(AgentCLI + AgentCLIToolbar + AgentCLIHistory + AgentCLIEmptyState + ChatInputArea + agent-cli-constants.ts)在 Q7/Q35 组件拆分后丢失了 U8 (v0.105) 的 i18n 集成——6 个文件中 ~66 行 UI 硬编码中文文本未接入 `useTranslation()` hook,切换 en-US 时仍显示中文。 + +**根因**: U8 (v0.105) 在原始 AgentCLI.tsx (814L) 上完成了 216 处中文清零。但后续 Q7 (v0.111) 将 AgentCLI 拆分为 AgentCLIToolbar/AgentCLIEmptyState/ScrollButtons + agent-cli-constants.ts,Q35 (v0.124) 又拆出 agent-cli/event-handler.ts + ChatInputArea.tsx。拆分过程中提取的文件未保留 i18n 集成,硬编码中文回归。 + +**已完成 (2026-07-12)**: + +1. **新增 `aiops.cli.*` 翻译命名空间** — 100 个翻译键,8 个子分类 (toolbar/safety/tips/error/info/viewMode/placeholder/history/empty/input) +2. **常量数组改为工厂函数** — `SAFETY_MODES`/`TIPS_HOST`/`TIPS_KUBERNETES` 从静态导出常量改为 `getSafetyModes(t)`/`getTipsHost(t)`/`getTipsKubernetes(t)` 接收 `TFunction` 参数的工厂函数(符合项目"工具文件收 TFunction 参数"约定) +3. **6 个组件文件迁移**: + - `AgentCLI.tsx` — subtitle/toast/error/placeholder/viewMode 共 22 处中文 → t() 调用 + - `AgentCLIToolbar.tsx` — 模式/目标选择/Pod/安全模式/历史/状态徽章/核心能力共 28 处 → t() + - `AgentCLIHistory.tsx` — toast/confirm/label/status 共 16 处 → t() + - `AgentCLIEmptyState.tsx` — 空状态文案 8 处 → t() + - `ChatInputArea.tsx` — hint/stop/execute 3 处 → t() + - `agent-cli-constants.ts` — SAFETY_MODES 3 模式 × 4 字段 + TIPS 2 × 4 × 2 字段全部通过工厂函数 → t() + +**验收标准**: +- [x] `aiops.cli.*` 命名空间 100 个语义化键,zh/en 完全对齐(7481 = 7481) +- [x] 6 个文件全部 UI 硬编码中文清零(剩余 CJK 仅为注释) +- [x] `tsc -b` 零错误 +- [x] `npm run build` 零错误(✓ built in 18.21s) +- [x] ESLint errors=0,any=0,unused=0,warnings=53(无新增) +- [x] `go build` 零错误 +- [x] 17/17 集成回归测试通过 +- [x] Docker 部署 healthy + diff --git a/frontend/src/components/aiops/AgentCLI.tsx b/frontend/src/components/aiops/AgentCLI.tsx index 6a0815b..ce1e42e 100644 --- a/frontend/src/components/aiops/AgentCLI.tsx +++ b/frontend/src/components/aiops/AgentCLI.tsx @@ -2,6 +2,7 @@ // 关联主机后,用自然语言描述任务,AI 通过 SSH 工具自主排障/部署,循环重试直到解决。 // 通过 WebSocket 实时展示思考、执行的命令、输出与结果;高危命令弹框确认。 import { useState, useRef, useEffect, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { useConfirm } from '@/components/ui/confirm-dialog'; @@ -24,6 +25,7 @@ import { createWsEventHandler } from './agent-cli/event-handler'; import ChatInputArea from './agent-cli/ChatInputArea'; export default function AgentCLI() { + const { t } = useTranslation(); const confirm = useConfirm(); const [cliMode, setCliMode] = useState('host'); @@ -155,8 +157,8 @@ export default function AgentCLI() { const connectTimer = window.setTimeout(() => { if (active && !everConnected && ws.readyState !== WebSocket.OPEN) { push({ kind: 'error', content: cliMode === 'kubernetes' - ? '连接集群超时:请检查集群凭据与大模型配置后重试。' - : '连接主机超时:请检查 SSH 凭据与大模型配置后重试。' }); + ? t('aiops.cli.error.connectTimeoutK8s') + : t('aiops.cli.error.connectTimeoutHost') }); try { ws.close(); } catch { /* noop */ } } }, 8000); @@ -169,10 +171,10 @@ export default function AgentCLI() { setRunning(false); if (!everConnected) { push({ kind: 'error', content: cliMode === 'kubernetes' - ? '集群连接被拒绝:请检查集群 kubeconfig/token 配置与大模型是否启用。' - : '主机连接被拒绝:请确认 SSH 凭据与大模型配置后重试。' }); + ? t('aiops.cli.error.connectRejectedK8s') + : t('aiops.cli.error.connectRejectedHost') }); } else if (!viewMode) { - push({ kind: 'info', content: '连接已关闭' }); + push({ kind: 'info', content: t('aiops.cli.info.connectionClosed') }); } }; ws.onerror = () => { setConnected(false); }; @@ -227,10 +229,10 @@ export default function AgentCLI() { const send = () => { const q = input.trim(); if (!q) return; - if (cliMode === 'host' && !serverId) { toast.error('请先选择目标主机'); return; } - if (cliMode === 'kubernetes' && (!clusterCode || !namespace)) { toast.error('请先选择集群和命名空间'); return; } + if (cliMode === 'host' && !serverId) { toast.error(t('aiops.cli.error.selectServer')); return; } + if (cliMode === 'kubernetes' && (!clusterCode || !namespace)) { toast.error(t('aiops.cli.error.selectCluster')); return; } if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { - toast.error('连接未就绪,请检查大模型配置或刷新页面重试'); + toast.error(t('aiops.cli.error.notReady')); return; } if (running) return; @@ -254,7 +256,10 @@ export default function AgentCLI() { setItems([ { id: nextId(), kind: 'info', - content: `查看历史记录:${sess.title}(${sess.status === 'done' ? '已完成' : sess.status})— 仅浏览,点击「继续追问」可在此基础上接着执行`, + content: t('aiops.cli.info.viewHistory', { + title: sess.title, + status: sess.status === 'done' ? t('aiops.cli.statusDone') : sess.status, + }), }, ...replayItems, ]); @@ -265,11 +270,11 @@ export default function AgentCLI() { const resumeHistorySession = () => { if (!viewSession) return; if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { - toast.error('连接未就绪,请稍后重试'); + toast.error(t('aiops.cli.error.notReadyRetry')); return; } wsRef.current.send(JSON.stringify({ type: 'resume', session_id: viewSession.id })); - push({ kind: 'info', content: `正在恢复会话「${viewSession.title}」…` }); + push({ kind: 'info', content: t('aiops.cli.info.resuming', { title: viewSession.title }) }); }; const handleNewTask = () => { @@ -287,7 +292,7 @@ export default function AgentCLI() { } className="h-full" contentClassName="h-full flex flex-col min-h-0 gap-2" @@ -340,15 +345,15 @@ export default function AgentCLI() { {viewMode && viewSession && (
- 历史记录(只读)· {viewSession.title} + {t('aiops.cli.viewMode.readOnly', { title: viewSession.title })} {canResumeView && ( )}
)} @@ -387,14 +392,14 @@ export default function AgentCLI() { running={running} viewMode={viewMode} placeholder={ - viewMode ? '查看模式中,点击「继续追问」或「新建任务」' - : canContinue ? '继续追问,回车发送(Shift+回车换行)' - : wsReady ? '输入任务描述,回车发送(Shift+回车换行)' - : (cliMode === 'kubernetes' ? '请先选择集群和命名空间' : '请先选择目标主机') + viewMode ? t('aiops.cli.placeholder.viewMode') + : canContinue ? t('aiops.cli.placeholder.continueInput') + : wsReady ? t('aiops.cli.placeholder.inputTask') + : (cliMode === 'kubernetes' ? t('aiops.cli.placeholder.selectClusterNs') : t('aiops.cli.placeholder.selectServerShort')) } />
); -} \ No newline at end of file +} diff --git a/frontend/src/components/aiops/AgentCLIEmptyState.tsx b/frontend/src/components/aiops/AgentCLIEmptyState.tsx index 7765756..395d2d0 100644 --- a/frontend/src/components/aiops/AgentCLIEmptyState.tsx +++ b/frontend/src/components/aiops/AgentCLIEmptyState.tsx @@ -1,7 +1,8 @@ // AgentCLIEmptyState.tsx — AgentCLI 对话区空状态(未选目标/连接中/就绪提示) +import { useTranslation } from 'react-i18next'; import { Terminal, Loader2 } from 'lucide-react'; import type { AgentCliMode } from '@/api/aiops/agent-cli'; -import { POD_NONE, SAFETY_MODES } from './agent-cli-constants'; +import { POD_NONE, getSafetyModes } from './agent-cli-constants'; export interface AgentCLIEmptyStateProps { cliMode: AgentCliMode; @@ -12,28 +13,30 @@ export interface AgentCLIEmptyStateProps { } export default function AgentCLIEmptyState({ cliMode, wsReady, connected, safetyMode, podName }: AgentCLIEmptyStateProps) { - const currentSafety = SAFETY_MODES.find((m) => m.value === safetyMode) ?? SAFETY_MODES[1]; + const { t } = useTranslation(); + const safetyModes = getSafetyModes(t); + const currentSafety = safetyModes.find((m) => m.value === safetyMode) ?? safetyModes[1]; const isK8s = cliMode === 'kubernetes'; return (
{!wsReady ? (
- {isK8s ? '请先选择集群和命名空间' : '请先选择一台目标主机'} + {isK8s ? t('aiops.cli.empty.selectK8s') : t('aiops.cli.empty.selectHost')}
) : !connected ? ( <> -
{isK8s ? '正在连接集群...' : '正在连接主机...'}
+
{isK8s ? t('aiops.cli.empty.connectingK8s') : t('aiops.cli.empty.connectingHost')}
) : (
{isK8s - ? '描述 Kubernetes 排障任务,例如「查看 NotReady 节点」或「分析 nginx Pod 重启原因」' - : '描述你要执行的任务,例如「检查磁盘使用率并清理大文件」'} + ? t('aiops.cli.empty.readyK8s') + : t('aiops.cli.empty.readyHost')}
- 当前安全模式:{currentSafety.label}({currentSafety.short}) - {isK8s && podName === POD_NONE && ' · 未选 Pod,仅集群/NS 级查询'} + {t('aiops.cli.empty.currentSafety', { label: currentSafety.label, short: currentSafety.short })} + {isK8s && podName === POD_NONE && t('aiops.cli.empty.noPod')}
)} diff --git a/frontend/src/components/aiops/AgentCLIHistory.tsx b/frontend/src/components/aiops/AgentCLIHistory.tsx index b40c00c..b4adcca 100644 --- a/frontend/src/components/aiops/AgentCLIHistory.tsx +++ b/frontend/src/components/aiops/AgentCLIHistory.tsx @@ -1,5 +1,6 @@ // AgentCLIHistory.tsx - Agent CLI 会话历史侧栏(列表/回放/删除/折叠) // Logan.Li import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; @@ -31,6 +32,7 @@ function statusBadge(status: string) { } export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, onView, onNewTask, refreshKey, onCollapse }: Props) { + const { t } = useTranslation(); const confirm = useConfirm(); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); @@ -57,11 +59,11 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, }); setSessions(res?.list || []); } catch { - toast.error('加载历史会话失败'); + toast.error(t('aiops.cli.history.loadFailed')); } finally { setLoading(false); } - }, [contextReady, mode, serverId, clusterId, namespace]); + }, [contextReady, mode, serverId, clusterId, namespace, t]); useEffect(() => { loadSessions(); }, [loadSessions, refreshKey]); @@ -72,17 +74,17 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, setActiveId(sess.id); onView(items, sess); } catch { - toast.error('加载会话详情失败'); + toast.error(t('aiops.cli.history.detailFailed')); } }; const handleDelete = async (e: React.MouseEvent, sess: AgentCliSession) => { e.stopPropagation(); const ok = await confirm({ - title: '删除会话', - description: `确定删除「${sess.title}」?此操作不可恢复。`, - confirmText: '删除', - cancelText: '取消', + title: t('aiops.cli.history.deleteTitle'), + description: t('aiops.cli.history.deleteDesc', { title: sess.title }), + confirmText: t('common.delete'), + cancelText: t('common.cancel'), variant: 'destructive', }); if (!ok) return; @@ -93,22 +95,29 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, onNewTask(); } loadSessions(); - toast.success('已删除'); + toast.success(t('aiops.cli.history.deleted')); } catch { - toast.error('删除失败'); + toast.error(t('aiops.cli.history.deleteFailed')); } }; + const sessionStatusText = (status: string) => { + if (status === 'done') return t('aiops.cli.history.statusDone'); + if (status === 'running') return t('aiops.cli.history.statusRunning'); + if (status === 'aborted') return t('aiops.cli.history.statusAborted'); + return t('aiops.cli.history.statusError'); + }; + return (
- 历史 + {t('aiops.cli.history.label')} {onCollapse && ( - )} @@ -116,12 +125,12 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, {!contextReady ? (
- {mode === 'kubernetes' ? '选择集群和命名空间后查看' : '选择主机后查看'} + {mode === 'kubernetes' ? t('aiops.cli.history.selectK8s') : t('aiops.cli.history.selectHost')}
) : loading ? (
) : sessions.length === 0 ? ( -
暂无历史
+
{t('aiops.cli.history.noHistory')}
) : (
{sessions.map((s) => ( @@ -129,7 +138,7 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace, key={s.id} type="button" onClick={() => handleView(s)} - title="查看历史记录" + title={t('aiops.cli.history.viewHistory')} className={`w-full text-left rounded-md px-2 py-1.5 transition-colors group ${ activeId === s.id ? 'bg-blue-500/15 border border-blue-500/30' : 'hover:bg-[var(--dg-bg-hover)]/60 border border-transparent' }`} @@ -138,11 +147,11 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace,
{s.title}
- {s.rounds}轮 · {s.total_tokens || 0}t + {t('aiops.cli.history.rounds', { rounds: s.rounds, tokens: s.total_tokens || 0 })}
- {s.status === 'done' ? '完成' : s.status === 'running' ? '进行' : s.status === 'aborted' ? '中断' : '失败'} + {sessionStatusText(s.status)}
); -} \ No newline at end of file +} diff --git a/frontend/src/components/aiops/AgentCLIToolbar.tsx b/frontend/src/components/aiops/AgentCLIToolbar.tsx index 33ab19e..788bed0 100644 --- a/frontend/src/components/aiops/AgentCLIToolbar.tsx +++ b/frontend/src/components/aiops/AgentCLIToolbar.tsx @@ -1,5 +1,6 @@ // AgentCLIToolbar.tsx — AgentCLI 工作区控制栏(从 AgentCLI.tsx 提取) // 包含:模式切换(主机/K8s) + 目标选择(主机/集群/NS/Pod) + 安全模式 + 状态徽章 + 核心能力说明 +import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { @@ -13,7 +14,7 @@ import { } from 'lucide-react'; import type { AgentCliMode } from '@/api/aiops/agent-cli'; import { - POD_NONE, TIPS_HOST, TIPS_KUBERNETES, SAFETY_MODES, formatServerLabel, + POD_NONE, getSafetyModes, getTipsHost, getTipsKubernetes, formatServerLabel, type TipItem, } from './agent-cli-constants'; @@ -55,6 +56,7 @@ export interface AgentCLIToolbarProps { } export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { + const { t } = useTranslation(); const { cliMode, setCliMode, servers, serverId, setServerId, @@ -66,7 +68,8 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { historyOpen, setHistoryOpen, } = props; - const currentTips: TipItem[] = cliMode === 'kubernetes' ? TIPS_KUBERNETES : TIPS_HOST; + const currentTips: TipItem[] = cliMode === 'kubernetes' ? getTipsKubernetes(t) : getTipsHost(t); + const safetyModes = getSafetyModes(t); const selectedServer = servers.find((s) => String(s.id) === serverId); const filteredPods = pods.filter((p) => { const q = podSearch.trim().toLowerCase(); @@ -82,7 +85,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { className={`px-2.5 text-xs flex items-center gap-1 ${cliMode === 'host' ? 'bg-blue-500/15 dg-text-blue' : 'dg-text-secondary hover:bg-[var(--dg-bg-hover)]'}`} onClick={() => setCliMode('host')} > - 主机 + {t('aiops.cli.host')} {filteredPods.map((p) => (
@@ -184,23 +187,23 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { - {SAFETY_MODES.map((m) => ( + {safetyModes.map((m) => ( {m.label} ))} - -
安全模式说明
+
{t('aiops.cli.toolbar.safetyTitle')}

- 控制 AI 执行远程命令前是否需要你确认。危险命令(如 rm -rf /)任何模式均硬性拦截。 + {t('aiops.cli.toolbar.safetyDesc')}

- {SAFETY_MODES.map((m) => ( + {safetyModes.map((m) => (
{m.label} {m.short} {'recommended' in m && m.recommended && ( - 推荐 + {t('aiops.cli.toolbar.safetyRecommended')} )}

{m.desc}

-

例:{m.examples}

+

{t('aiops.cli.toolbar.safetyExample')}:{m.examples}

))} @@ -230,10 +233,10 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { size="sm" className="h-8 px-2 text-xs dg-text-secondary" onClick={() => setHistoryOpen((v) => !v)} - title={historyOpen ? '收起历史任务' : '展开历史任务'} + title={historyOpen ? t('aiops.cli.toolbar.historyCollapse') : t('aiops.cli.toolbar.historyExpand')} > {historyOpen ? : } - 历史 + {t('aiops.cli.toolbar.historyLabel')}
@@ -241,36 +244,36 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) { {/* 状态徽章 */} {connected && ( - 已连接 + {t('aiops.cli.toolbar.connected')} )} {viewMode && ( - 查看记录 + {t('aiops.cli.toolbar.viewRecord')} )} {canContinue && !running && !viewMode && ( - 可追问 + {t('aiops.cli.toolbar.canContinue')} )} {running && ( - 第 {round} 轮 · {tokens}t + {t('aiops.cli.toolbar.round', { round, tokens })} )} {/* 核心能力说明 Popover */} - -
核心能力
- {currentTips.map((t) => ( -
- +
{t('aiops.cli.toolbar.capabilitiesTitle')}
+ {currentTips.map((tip) => ( +
+
-
{t.title}
-
{t.desc}
+
{tip.title}
+
{tip.desc}
))} diff --git a/frontend/src/components/aiops/agent-cli-constants.ts b/frontend/src/components/aiops/agent-cli-constants.ts index bc5541d..391197d 100644 --- a/frontend/src/components/aiops/agent-cli-constants.ts +++ b/frontend/src/components/aiops/agent-cli-constants.ts @@ -3,6 +3,7 @@ import { Cpu, Shield, Brain, Database, Terminal, Cloud, } from 'lucide-react'; +import type { TFunction } from 'i18next'; // WS 事件类型(后端 wsEvent 对应) export interface WsEvent { @@ -33,55 +34,70 @@ export interface TipItem { desc: string; } -// 四大核心模块说明(灯泡 Popover)— 主机模式 -export const TIPS_HOST: TipItem[] = [ - { icon: Cpu, title: 'LLM + 工具层', color: 'text-sky-400', - desc: 'OpenAI 兼容 LLM;工具含 ssh_execute(远程执行)与 analyze_output(输出分析)。' }, - { icon: Shield, title: '安全 + Prompt', color: 'dg-text-yellow', - desc: '五级风险评估 + 三级安全模式;危险命令硬性拒绝,高危命令弹框确认。' }, - { icon: Brain, title: 'ReAct 核心引擎', color: 'dg-text-purple', - desc: '思考→行动→观察循环(最多 16 轮),失败换法重试,循环检测防死循环。' }, - { icon: Database, title: '自进化引擎', color: 'dg-text-green', - desc: '四层记忆异步学习,同类任务自动注入历史经验,越用越聪明。' }, -]; +export interface SafetyMode { + value: string; + label: string; + short: string; + desc: string; + examples: string; + recommended?: boolean; +} -// 四大核心模块说明(灯泡 Popover)— Kubernetes 模式 -export const TIPS_KUBERNETES: TipItem[] = [ - { icon: Cloud, title: 'Kubernetes 查询', color: 'text-sky-400', - desc: 'kubernetes_get 统一查询节点/Pod/Deployment/事件等;pod_logs 拉取容器日志。' }, - { icon: Terminal, title: 'Pod 执行', color: 'dg-text-purple', - desc: '选定 Pod 后可用 pod_execute 进入容器排障;kubernetes_action 执行重启/扩缩容等变更。' }, - { icon: Shield, title: '安全 + ReAct', color: 'dg-text-yellow', - desc: '只读工具自动执行;变更与危险命令弹框确认,最多 16 轮循环排障。' }, - { icon: Database, title: '自进化引擎', color: 'dg-text-green', - desc: '按集群/命名空间分桶学习,同类 Kubernetes 排障任务越用越准。' }, -]; +// getTipsHost — 主机模式四大核心模块说明(接收 TFunction 参数) +export function getTipsHost(t: TFunction): TipItem[] { + return [ + { icon: Cpu, title: t('aiops.cli.tips.host.llmTitle'), color: 'text-sky-400', + desc: t('aiops.cli.tips.host.llmDesc') }, + { icon: Shield, title: t('aiops.cli.tips.host.shieldTitle'), color: 'dg-text-yellow', + desc: t('aiops.cli.tips.host.shieldDesc') }, + { icon: Brain, title: t('aiops.cli.tips.host.brainTitle'), color: 'dg-text-purple', + desc: t('aiops.cli.tips.host.brainDesc') }, + { icon: Database, title: t('aiops.cli.tips.host.dbTitle'), color: 'dg-text-green', + desc: t('aiops.cli.tips.host.dbDesc') }, + ]; +} -// 安全模式说明(灯泡 Popover 展示) -export const SAFETY_MODES = [ - { - value: 'strict', - label: '严格', - short: '中风险+确认', - desc: 'chmod、文件修改、服务重启等中风险及以上命令均需弹框确认。适合生产环境或不熟悉的主机。', - examples: 'chmod、mv、systemctl restart', - }, - { - value: 'balanced', - label: '均衡', - short: '高风险+确认', - desc: '日常排障命令(查看日志、ps、df)自动执行;删除文件、重启服务等高风险操作才需确认。推荐日常使用。', - examples: 'cat/grep/df 自动 · rm/reboot 确认', - recommended: true, - }, - { - value: 'permissive', - label: '宽松', - short: '仅拦截危险', - desc: '除 rm -rf /、mkfs 等危险命令硬性拦截外,其余全部自动执行。仅适合隔离测试环境。', - examples: 'rm -rf / 拦截 · 其余自动', - }, -] as const; +// getTipsK8s — Kubernetes 模式四大核心模块说明(接收 TFunction 参数) +export function getTipsKubernetes(t: TFunction): TipItem[] { + return [ + { icon: Cloud, title: t('aiops.cli.tips.k8s.cloudTitle'), color: 'text-sky-400', + desc: t('aiops.cli.tips.k8s.cloudDesc') }, + { icon: Terminal, title: t('aiops.cli.tips.k8s.terminalTitle'), color: 'dg-text-purple', + desc: t('aiops.cli.tips.k8s.terminalDesc') }, + { icon: Shield, title: t('aiops.cli.tips.k8s.shieldTitle'), color: 'dg-text-yellow', + desc: t('aiops.cli.tips.k8s.shieldDesc') }, + { icon: Database, title: t('aiops.cli.tips.k8s.dbTitle'), color: 'dg-text-green', + desc: t('aiops.cli.tips.k8s.dbDesc') }, + ]; +} + +// getSafetyModes — 安全模式说明(接收 TFunction 参数) +export function getSafetyModes(t: TFunction): SafetyMode[] { + return [ + { + value: 'strict', + label: t('aiops.cli.safety.strict.label'), + short: t('aiops.cli.safety.strict.short'), + desc: t('aiops.cli.safety.strict.desc'), + examples: t('aiops.cli.safety.strict.examples'), + }, + { + value: 'balanced', + label: t('aiops.cli.safety.balanced.label'), + short: t('aiops.cli.safety.balanced.short'), + desc: t('aiops.cli.safety.balanced.desc'), + examples: t('aiops.cli.safety.balanced.examples'), + recommended: true, + }, + { + value: 'permissive', + label: t('aiops.cli.safety.permissive.label'), + short: t('aiops.cli.safety.permissive.short'), + desc: t('aiops.cli.safety.permissive.desc'), + examples: t('aiops.cli.safety.permissive.examples'), + }, + ]; +} // formatServerLabel 主机下拉展示文案 export function formatServerLabel(s: { hostname: string; ip: string }) { diff --git a/frontend/src/components/aiops/agent-cli/ChatInputArea.tsx b/frontend/src/components/aiops/agent-cli/ChatInputArea.tsx index 2c04a91..f4ab4af 100644 --- a/frontend/src/components/aiops/agent-cli/ChatInputArea.tsx +++ b/frontend/src/components/aiops/agent-cli/ChatInputArea.tsx @@ -1,5 +1,6 @@ // ChatInputArea.tsx — AgentCLI 输入区(textarea + 发送/停止按钮) // 从 AgentCLI.tsx 提取:受控展示组件,接收输入值 + 状态 + 回调 +import { useTranslation } from 'react-i18next'; import { Send, Square } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -16,6 +17,7 @@ export interface ChatInputAreaProps { export default function ChatInputArea({ input, setInput, onSend, onStop, running, viewMode, placeholder, }: ChatInputAreaProps) { + const { t } = useTranslation(); return (