refactor(i18n): U31 AIOps Agent CLI 组件 i18n 修复 — Q7/Q35 拆分后丢失的 i18n 集成全部修复
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 测试通过
This commit is contained in:
1 parent
6dfddb426c
commit
4f2d279865
9 files changed
+461
-128
No files matched your search
@@ -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<AgentCliMode>('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() {
|
||||
<PageShell
|
||||
variant="workspace"
|
||||
title="Agent CLI"
|
||||
subtitle={cliMode === 'kubernetes' ? '自然语言驱动 Kubernetes 集群查询与 Pod 排障' : '自然语言驱动 SSH 自主排障与部署'}
|
||||
subtitle={cliMode === 'kubernetes' ? t('aiops.cli.subtitleK8s') : t('aiops.cli.subtitleHost')}
|
||||
icon={<Bot className="h-5 w-5" />}
|
||||
className="h-full"
|
||||
contentClassName="h-full flex flex-col min-h-0 gap-2"
|
||||
@@ -340,15 +345,15 @@ export default function AgentCLI() {
|
||||
{viewMode && viewSession && (
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-[var(--dg-border)] bg-amber-500/5 shrink-0 flex-wrap">
|
||||
<span className="text-xs dg-text-secondary flex-1 min-w-[12rem]">
|
||||
历史记录(只读)· {viewSession.title}
|
||||
{t('aiops.cli.viewMode.readOnly', { title: viewSession.title })}
|
||||
</span>
|
||||
{canResumeView && (
|
||||
<Button size="sm" variant="outline" className="h-7 text-xs" onClick={resumeHistorySession}>
|
||||
继续追问
|
||||
{t('aiops.cli.viewMode.continueBtn')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" variant="ghost" className="h-7 text-xs" onClick={handleNewTask}>
|
||||
新建任务
|
||||
{t('aiops.cli.viewMode.newTask')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -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'))
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="h-full flex flex-col items-center justify-center dg-text-muted gap-3">
|
||||
<Terminal className="w-10 h-10 opacity-40" />
|
||||
{!wsReady ? (
|
||||
<div className="text-sm">
|
||||
{isK8s ? '请先选择集群和命名空间' : '请先选择一台目标主机'}
|
||||
{isK8s ? t('aiops.cli.empty.selectK8s') : t('aiops.cli.empty.selectHost')}
|
||||
</div>
|
||||
) : !connected ? (
|
||||
<>
|
||||
<Loader2 className="w-6 h-6 animate-spin dg-text-blue" />
|
||||
<div className="text-sm">{isK8s ? '正在连接集群...' : '正在连接主机...'}</div>
|
||||
<div className="text-sm">{isK8s ? t('aiops.cli.empty.connectingK8s') : t('aiops.cli.empty.connectingHost')}</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm text-center max-w-md">
|
||||
{isK8s
|
||||
? '描述 Kubernetes 排障任务,例如「查看 NotReady 节点」或「分析 nginx Pod 重启原因」'
|
||||
: '描述你要执行的任务,例如「检查磁盘使用率并清理大文件」'}
|
||||
? t('aiops.cli.empty.readyK8s')
|
||||
: t('aiops.cli.empty.readyHost')}
|
||||
<div className="text-xs dg-text-muted mt-2">
|
||||
当前安全模式:{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')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<AgentCliSession[]>([]);
|
||||
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 (
|
||||
<div className="w-56 shrink-0 flex flex-col bg-[var(--dg-bg-secondary)]/60 border border-[var(--dg-border)] rounded-lg overflow-hidden">
|
||||
<div className="flex items-center gap-1.5 px-2.5 py-2 border-b border-[var(--dg-border)]">
|
||||
<History className="w-3.5 h-3.5 dg-text-secondary shrink-0" />
|
||||
<span className="text-xs font-medium text-[var(--dg-text-primary)] flex-1 truncate">历史</span>
|
||||
<span className="text-xs font-medium text-[var(--dg-text-primary)] flex-1 truncate">{t('aiops.cli.history.label')}</span>
|
||||
<Button variant="ghost" size="sm" className="h-6 px-1.5 text-[10px]" onClick={() => { setActiveId(null); onNewTask(); }}>
|
||||
<Plus className="w-3 h-3 mr-0.5" />新建
|
||||
<Plus className="w-3 h-3 mr-0.5" />{t('aiops.cli.history.newBtn')}
|
||||
</Button>
|
||||
{onCollapse && (
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 dg-text-muted hover:dg-text-secondary" onClick={onCollapse} title="收起历史">
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 dg-text-muted hover:dg-text-secondary" onClick={onCollapse} title={t('aiops.cli.history.collapseHistory')}>
|
||||
<PanelLeftClose className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
)}
|
||||
@@ -116,12 +125,12 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace,
|
||||
<ScrollArea className="flex-1 min-h-0">
|
||||
{!contextReady ? (
|
||||
<div className="p-3 text-[10px] dg-text-muted text-center">
|
||||
{mode === 'kubernetes' ? '选择集群和命名空间后查看' : '选择主机后查看'}
|
||||
{mode === 'kubernetes' ? t('aiops.cli.history.selectK8s') : t('aiops.cli.history.selectHost')}
|
||||
</div>
|
||||
) : loading ? (
|
||||
<div className="p-3 flex justify-center"><Loader2 className="w-4 h-4 animate-spin dg-text-muted" /></div>
|
||||
) : sessions.length === 0 ? (
|
||||
<div className="p-3 text-[10px] dg-text-muted text-center">暂无历史</div>
|
||||
<div className="p-3 text-[10px] dg-text-muted text-center">{t('aiops.cli.history.noHistory')}</div>
|
||||
) : (
|
||||
<div className="p-1 space-y-0.5">
|
||||
{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,
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-[11px] text-[var(--dg-text-primary)] truncate leading-tight">{s.title}</div>
|
||||
<div className="text-[10px] dg-text-muted mt-0.5">
|
||||
{s.rounds}轮 · {s.total_tokens || 0}t
|
||||
{t('aiops.cli.history.rounds', { rounds: s.rounds, tokens: s.total_tokens || 0 })}
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`text-[9px] px-1 py-0 border shrink-0 ${statusBadge(s.status)}`}>
|
||||
{s.status === 'done' ? '完成' : s.status === 'running' ? '进行' : s.status === 'aborted' ? '中断' : '失败'}
|
||||
{sessionStatusText(s.status)}
|
||||
</Badge>
|
||||
<button
|
||||
type="button"
|
||||
@@ -159,4 +168,4 @@ export default function AgentCLIHistory({ mode, serverId, clusterId, namespace,
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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')}
|
||||
>
|
||||
<Server className="w-3.5 h-3.5" />主机
|
||||
<Server className="w-3.5 h-3.5" />{t('aiops.cli.host')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -98,7 +101,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
<Select value={serverId} onValueChange={setServerId}>
|
||||
<SelectTrigger className="w-80 max-w-[22rem] h-8 text-xs dg-input min-w-0">
|
||||
<Server className="w-3.5 h-3.5 mr-1.5 dg-text-secondary shrink-0" />
|
||||
<SelectValue placeholder="选择目标主机" className="truncate">
|
||||
<SelectValue placeholder={t('aiops.cli.toolbar.selectServer')} className="truncate">
|
||||
{selectedServer ? formatServerLabel(selectedServer) : undefined}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
@@ -115,7 +118,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
<Select value={clusterCode} onValueChange={setClusterCode}>
|
||||
<SelectTrigger className="w-44 h-8 text-xs dg-input">
|
||||
<Cloud className="w-3.5 h-3.5 mr-1 dg-text-secondary shrink-0" />
|
||||
<SelectValue placeholder="集群" />
|
||||
<SelectValue placeholder={t('aiops.cli.toolbar.cluster')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="dg-input max-h-72">
|
||||
{clusters.map((c) => (
|
||||
@@ -125,7 +128,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
</Select>
|
||||
<Select value={namespace} onValueChange={setNamespace}>
|
||||
<SelectTrigger className="w-36 h-8 text-xs dg-input">
|
||||
<SelectValue placeholder="命名空间" />
|
||||
<SelectValue placeholder={t('aiops.cli.toolbar.namespace')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="dg-input max-h-72">
|
||||
{namespaces.map((ns) => (
|
||||
@@ -138,14 +141,14 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="h-8 text-xs dg-input min-w-[10rem] justify-start font-normal">
|
||||
<Terminal className="w-3.5 h-3.5 mr-1 dg-text-secondary shrink-0" />
|
||||
{podName === POD_NONE ? 'Pod(可选)' : podName}
|
||||
{podName === POD_NONE ? t('aiops.cli.toolbar.podOptional') : podName}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-80 dg-input p-2 space-y-2">
|
||||
<Input
|
||||
value={podSearch}
|
||||
onChange={(e) => setPodSearch(e.target.value)}
|
||||
placeholder="搜索 Pod 名称"
|
||||
placeholder={t('aiops.cli.toolbar.podSearch')}
|
||||
className="h-8 text-xs dg-input"
|
||||
/>
|
||||
<div className="max-h-56 overflow-y-auto space-y-0.5">
|
||||
@@ -154,7 +157,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
className={`w-full text-left text-xs px-2 py-1.5 rounded ${podName === POD_NONE ? 'bg-blue-500/15' : 'hover:bg-[var(--dg-bg-hover)]'}`}
|
||||
onClick={() => setPodName(POD_NONE)}
|
||||
>
|
||||
不选定 Pod(集群/NS 级任务)
|
||||
{t('aiops.cli.toolbar.podNone')}
|
||||
</button>
|
||||
{filteredPods.map((p) => (
|
||||
<button
|
||||
@@ -168,7 +171,7 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
</button>
|
||||
))}
|
||||
{filteredPods.length === 0 && (
|
||||
<div className="text-[10px] dg-text-muted text-center py-2">无匹配 Pod</div>
|
||||
<div className="text-[10px] dg-text-muted text-center py-2">{t('aiops.cli.toolbar.podNoMatch')}</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
@@ -184,23 +187,23 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="dg-input">
|
||||
{SAFETY_MODES.map((m) => (
|
||||
{safetyModes.map((m) => (
|
||||
<SelectItem key={m.value} value={m.value}>{m.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-yellow hover:bg-[var(--dg-bg-hover)]" title="安全模式说明">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-yellow hover:bg-[var(--dg-bg-hover)]" title={t('aiops.cli.toolbar.safetyTitle')}>
|
||||
<Lightbulb className="w-4 h-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-80 dg-input p-3 space-y-2.5">
|
||||
<div className="text-sm font-medium text-[var(--dg-text-primary)]">安全模式说明</div>
|
||||
<div className="text-sm font-medium text-[var(--dg-text-primary)]">{t('aiops.cli.toolbar.safetyTitle')}</div>
|
||||
<p className="text-[11px] dg-text-muted leading-relaxed">
|
||||
控制 AI 执行远程命令前是否需要你确认。危险命令(如 rm -rf /)任何模式均硬性拦截。
|
||||
{t('aiops.cli.toolbar.safetyDesc')}
|
||||
</p>
|
||||
{SAFETY_MODES.map((m) => (
|
||||
{safetyModes.map((m) => (
|
||||
<div
|
||||
key={m.value}
|
||||
className={`rounded-md p-2.5 border text-xs ${
|
||||
@@ -213,11 +216,11 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
<span className="font-medium text-[var(--dg-text-primary)]">{m.label}</span>
|
||||
<span className="text-[10px] dg-text-muted">{m.short}</span>
|
||||
{'recommended' in m && m.recommended && (
|
||||
<Badge className="text-[9px] px-1 py-0 bg-violet-500/15 dg-text-purple border-violet-500/30">推荐</Badge>
|
||||
<Badge className="text-[9px] px-1 py-0 bg-violet-500/15 dg-text-purple border-violet-500/30">{t('aiops.cli.toolbar.safetyRecommended')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="dg-text-secondary leading-relaxed mt-1">{m.desc}</p>
|
||||
<p className="text-[10px] dg-text-muted mt-1">例:{m.examples}</p>
|
||||
<p className="text-[10px] dg-text-muted mt-1">{t('aiops.cli.toolbar.safetyExample')}:{m.examples}</p>
|
||||
</div>
|
||||
))}
|
||||
</PopoverContent>
|
||||
@@ -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 ? <PanelLeftClose className="w-3.5 h-3.5 mr-1" /> : <History className="w-3.5 h-3.5 mr-1" />}
|
||||
历史
|
||||
{t('aiops.cli.toolbar.historyLabel')}
|
||||
</Button>
|
||||
|
||||
<div className="flex-1 min-w-[1rem]" />
|
||||
@@ -241,36 +244,36 @@ export default function AgentCLIToolbar(props: AgentCLIToolbarProps) {
|
||||
{/* 状态徽章 */}
|
||||
{connected && (
|
||||
<Badge className="h-6 text-[10px] bg-emerald-500/15 dg-text-green border-emerald-500/30">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 mr-1" />已连接
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 mr-1" />{t('aiops.cli.toolbar.connected')}
|
||||
</Badge>
|
||||
)}
|
||||
{viewMode && (
|
||||
<Badge className="h-6 text-[10px] bg-amber-500/15 dg-text-yellow border-amber-500/30">查看记录</Badge>
|
||||
<Badge className="h-6 text-[10px] bg-amber-500/15 dg-text-yellow border-amber-500/30">{t('aiops.cli.toolbar.viewRecord')}</Badge>
|
||||
)}
|
||||
{canContinue && !running && !viewMode && (
|
||||
<Badge className="h-6 text-[10px] bg-violet-500/15 dg-text-purple border-violet-500/30">可追问</Badge>
|
||||
<Badge className="h-6 text-[10px] bg-violet-500/15 dg-text-purple border-violet-500/30">{t('aiops.cli.toolbar.canContinue')}</Badge>
|
||||
)}
|
||||
{running && (
|
||||
<Badge className="h-6 text-[10px] bg-blue-500/15 dg-text-blue border-blue-500/30">
|
||||
<Loader2 className="w-3 h-3 mr-1 animate-spin" />第 {round} 轮 · {tokens}t
|
||||
<Loader2 className="w-3 h-3 mr-1 animate-spin" />{t('aiops.cli.toolbar.round', { round, tokens })}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{/* 核心能力说明 Popover */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-muted hover:dg-text-secondary" title="核心能力说明">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-muted hover:dg-text-secondary" title={t('aiops.cli.toolbar.capabilitiesTooltip')}>
|
||||
<Lightbulb className="w-4 h-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-80 dg-input p-3 space-y-2">
|
||||
<div className="text-sm font-medium text-[var(--dg-text-primary)]">核心能力</div>
|
||||
{currentTips.map((t) => (
|
||||
<div key={t.title} className="flex gap-2 p-2 rounded-md bg-[var(--dg-bg-tertiary)]/40 border border-[var(--dg-border)]">
|
||||
<t.icon className={`w-4 h-4 mt-0.5 shrink-0 ${t.color}`} />
|
||||
<div className="text-sm font-medium text-[var(--dg-text-primary)]">{t('aiops.cli.toolbar.capabilitiesTitle')}</div>
|
||||
{currentTips.map((tip) => (
|
||||
<div key={tip.title} className="flex gap-2 p-2 rounded-md bg-[var(--dg-bg-tertiary)]/40 border border-[var(--dg-border)]">
|
||||
<tip.icon className={`w-4 h-4 mt-0.5 shrink-0 ${tip.color}`} />
|
||||
<div>
|
||||
<div className="text-xs font-medium text-[var(--dg-text-primary)]">{t.title}</div>
|
||||
<div className="text-[10px] dg-text-secondary leading-relaxed mt-0.5">{t.desc}</div>
|
||||
<div className="text-xs font-medium text-[var(--dg-text-primary)]">{tip.title}</div>
|
||||
<div className="text-[10px] dg-text-secondary leading-relaxed mt-0.5">{tip.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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 (
|
||||
<div className="border-t border-[var(--dg-border)] p-3 space-y-2 shrink-0">
|
||||
<textarea
|
||||
@@ -28,14 +30,14 @@ export default function ChatInputArea({
|
||||
className="w-full resize-none dg-input min-h-[5rem] max-h-40 text-sm leading-relaxed disabled:opacity-50"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[10px] dg-text-muted">Shift+回车换行 · 回车发送</span>
|
||||
<span className="text-[10px] dg-text-muted">{t('aiops.cli.input.hint')}</span>
|
||||
{running ? (
|
||||
<Button variant="destructive" size="sm" onClick={onStop} className="bg-red-600 hover:bg-red-700">
|
||||
<Square className="w-3.5 h-3.5 mr-1" /> 停止
|
||||
<Square className="w-3.5 h-3.5 mr-1" /> {t('aiops.cli.input.stop')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" onClick={onSend} disabled={!input.trim() || viewMode} className="bg-blue-600 hover:bg-blue-700">
|
||||
<Send className="w-3.5 h-3.5 mr-1" /> 执行
|
||||
<Send className="w-3.5 h-3.5 mr-1" /> {t('aiops.cli.input.execute')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7886,7 +7886,139 @@
|
||||
"predictIn7d": "In 7 days",
|
||||
"predictIn30d": "In 30 days",
|
||||
"agentCliErrorOccurred": "Error occurred",
|
||||
"agentCliNoOutput": "(no output)"
|
||||
"agentCliNoOutput": "(no output)",
|
||||
"cli": {
|
||||
"host": "Host",
|
||||
"toolbar": {
|
||||
"selectServer": "Select target host",
|
||||
"cluster": "Cluster",
|
||||
"namespace": "Namespace",
|
||||
"podOptional": "Pod (optional)",
|
||||
"podSearch": "Search Pod name",
|
||||
"podNone": "No Pod selected (cluster/NS level tasks)",
|
||||
"podNoMatch": "No matching Pods",
|
||||
"safetyTitle": "Safety Mode Description",
|
||||
"safetyDesc": "Controls whether you need to confirm before AI executes remote commands. Dangerous commands are always blocked in all modes.",
|
||||
"safetyRecommended": "Recommended",
|
||||
"safetyExample": "e.g.",
|
||||
"historyExpand": "Expand history tasks",
|
||||
"historyCollapse": "Collapse history tasks",
|
||||
"historyLabel": "History",
|
||||
"connected": "Connected",
|
||||
"viewRecord": "View Record",
|
||||
"canContinue": "Can Continue",
|
||||
"capabilitiesTitle": "Core Capabilities",
|
||||
"capabilitiesTooltip": "Core Capabilities",
|
||||
"round": "Round {{round}} / {{tokens}}t"
|
||||
},
|
||||
"safety": {
|
||||
"strict": {
|
||||
"label": "Strict",
|
||||
"short": "Medium risk + confirm",
|
||||
"desc": "Medium-risk and above commands (chmod, file modifications, service restarts) require confirmation. Suitable for production or unfamiliar hosts.",
|
||||
"examples": "chmod, mv, systemctl restart"
|
||||
},
|
||||
"balanced": {
|
||||
"label": "Balanced",
|
||||
"short": "High risk + confirm",
|
||||
"desc": "Daily troubleshooting commands (viewing logs, ps, df) run automatically; only high-risk operations like file deletion or service restarts require confirmation. Recommended for daily use.",
|
||||
"examples": "cat/grep/df auto / rm/reboot confirm"
|
||||
},
|
||||
"permissive": {
|
||||
"label": "Permissive",
|
||||
"short": "Block danger only",
|
||||
"desc": "Except for dangerous commands which are hard-blocked, everything else runs automatically. Only suitable for isolated test environments.",
|
||||
"examples": "rm -rf / blocked / rest auto"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
"host": {
|
||||
"llmTitle": "LLM + Tool Layer",
|
||||
"llmDesc": "OpenAI-compatible LLM; tools include ssh_execute (remote execution) and analyze_output (output analysis).",
|
||||
"shieldTitle": "Security + Prompt",
|
||||
"shieldDesc": "Five-level risk assessment + three-level safety mode; dangerous commands hard-rejected, high-risk commands require confirmation.",
|
||||
"brainTitle": "ReAct Core Engine",
|
||||
"brainDesc": "Think-Act-Observe loop (max 16 rounds), retries with different approaches on failure, loop detection prevents infinite loops.",
|
||||
"dbTitle": "Self-Evolution Engine",
|
||||
"dbDesc": "Four-layer memory async learning, automatically injects historical experience for similar tasks, gets smarter with use."
|
||||
},
|
||||
"k8s": {
|
||||
"cloudTitle": "Kubernetes Query",
|
||||
"cloudDesc": "kubernetes_get unified query for nodes/Pods/Deployments/events; pod_logs fetches container logs.",
|
||||
"terminalTitle": "Pod Execution",
|
||||
"terminalDesc": "After selecting a Pod, use pod_execute to enter the container for troubleshooting; kubernetes_action performs restarts/scaling and other changes.",
|
||||
"shieldTitle": "Security + ReAct",
|
||||
"shieldDesc": "Read-only tools run automatically; changes and dangerous commands require confirmation, max 16 rounds of troubleshooting.",
|
||||
"dbTitle": "Self-Evolution Engine",
|
||||
"dbDesc": "Learns per cluster/namespace bucket, Kubernetes troubleshooting tasks become more accurate with use."
|
||||
}
|
||||
},
|
||||
"subtitleHost": "Natural language driven SSH autonomous troubleshooting and deployment",
|
||||
"subtitleK8s": "Natural language driven Kubernetes cluster queries and Pod troubleshooting",
|
||||
"error": {
|
||||
"connectTimeoutK8s": "Cluster connection timeout: please check cluster credentials and LLM configuration and retry.",
|
||||
"connectTimeoutHost": "Host connection timeout: please check SSH credentials and LLM configuration and retry.",
|
||||
"connectRejectedK8s": "Cluster connection rejected: please check cluster kubeconfig/token configuration and whether LLM is enabled.",
|
||||
"connectRejectedHost": "Host connection rejected: please confirm SSH credentials and LLM configuration and retry.",
|
||||
"selectServer": "Please select a target host first",
|
||||
"selectCluster": "Please select a cluster and namespace first",
|
||||
"notReady": "Connection not ready, please check LLM configuration or refresh the page",
|
||||
"notReadyRetry": "Connection not ready, please try again later"
|
||||
},
|
||||
"info": {
|
||||
"connectionClosed": "Connection closed",
|
||||
"viewHistory": "Viewing history: {{title}} ({{status}}) - read-only, click \"Continue\" to resume execution",
|
||||
"resuming": "Resuming session \"{{title}}\"..."
|
||||
},
|
||||
"statusDone": "Completed",
|
||||
"viewMode": {
|
||||
"readOnly": "History (read-only) - {{title}}",
|
||||
"continueBtn": "Continue",
|
||||
"newTask": "New Task"
|
||||
},
|
||||
"placeholder": {
|
||||
"viewMode": "View mode, click \"Continue\" or \"New Task\"",
|
||||
"continueInput": "Continue, press Enter to send (Shift+Enter for new line)",
|
||||
"inputTask": "Describe your task, press Enter to send (Shift+Enter for new line)",
|
||||
"selectClusterNs": "Please select a cluster and namespace first",
|
||||
"selectServerShort": "Please select a target host first"
|
||||
},
|
||||
"history": {
|
||||
"loadFailed": "Failed to load history sessions",
|
||||
"detailFailed": "Failed to load session details",
|
||||
"deleteTitle": "Delete Session",
|
||||
"deleteDesc": "Are you sure you want to delete \"{{title}}\"? This action cannot be undone.",
|
||||
"deleted": "Deleted",
|
||||
"deleteFailed": "Delete failed",
|
||||
"label": "History",
|
||||
"newBtn": "New",
|
||||
"collapseHistory": "Collapse history",
|
||||
"selectK8s": "Select cluster and namespace to view",
|
||||
"selectHost": "Select a host to view",
|
||||
"noHistory": "No history",
|
||||
"viewHistory": "View history record",
|
||||
"rounds": "{{rounds}} rounds / {{tokens}}t",
|
||||
"statusDone": "Done",
|
||||
"statusRunning": "Running",
|
||||
"statusAborted": "Aborted",
|
||||
"statusError": "Error"
|
||||
},
|
||||
"empty": {
|
||||
"selectK8s": "Please select a cluster and namespace first",
|
||||
"selectHost": "Please select a target host first",
|
||||
"connectingK8s": "Connecting to cluster...",
|
||||
"connectingHost": "Connecting to host...",
|
||||
"readyK8s": "Describe a Kubernetes troubleshooting task, e.g. \"Check NotReady nodes\" or \"Analyze nginx Pod restart cause\"",
|
||||
"readyHost": "Describe the task you want to execute, e.g. \"Check disk usage and clean up large files\"",
|
||||
"currentSafety": "Current safety mode: {{label}} ({{short}})",
|
||||
"noPod": " / No Pod selected, cluster/NS level queries only"
|
||||
},
|
||||
"input": {
|
||||
"hint": "Shift+Enter for new line / Enter to send",
|
||||
"stop": "Stop",
|
||||
"execute": "Run"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breadcrumb": {
|
||||
"dashboard": "Dashboard",
|
||||
|
||||
@@ -7886,7 +7886,139 @@
|
||||
"predictIn7d": "7天后",
|
||||
"predictIn30d": "30天后",
|
||||
"agentCliErrorOccurred": "发生错误",
|
||||
"agentCliNoOutput": "(无输出)"
|
||||
"agentCliNoOutput": "(无输出)",
|
||||
"cli": {
|
||||
"host": "主机",
|
||||
"toolbar": {
|
||||
"selectServer": "选择目标主机",
|
||||
"cluster": "集群",
|
||||
"namespace": "命名空间",
|
||||
"podOptional": "Pod(可选)",
|
||||
"podSearch": "搜索 Pod 名称",
|
||||
"podNone": "不选定 Pod(集群/NS 级任务)",
|
||||
"podNoMatch": "无匹配 Pod",
|
||||
"safetyTitle": "安全模式说明",
|
||||
"safetyDesc": "控制 AI 执行远程命令前是否需要你确认。危险命令(如 rm -rf /)任何模式均硬性拦截。",
|
||||
"safetyRecommended": "推荐",
|
||||
"safetyExample": "例",
|
||||
"historyExpand": "展开历史任务",
|
||||
"historyCollapse": "收起历史任务",
|
||||
"historyLabel": "历史",
|
||||
"connected": "已连接",
|
||||
"viewRecord": "查看记录",
|
||||
"canContinue": "可追问",
|
||||
"capabilitiesTitle": "核心能力",
|
||||
"capabilitiesTooltip": "核心能力说明",
|
||||
"round": "第 {{round}} 轮 · {{tokens}}t"
|
||||
},
|
||||
"safety": {
|
||||
"strict": {
|
||||
"label": "严格",
|
||||
"short": "中风险+确认",
|
||||
"desc": "chmod、文件修改、服务重启等中风险及以上命令均需弹框确认。适合生产环境或不熟悉的主机。",
|
||||
"examples": "chmod、mv、systemctl restart"
|
||||
},
|
||||
"balanced": {
|
||||
"label": "均衡",
|
||||
"short": "高风险+确认",
|
||||
"desc": "日常排障命令(查看日志、ps、df)自动执行;删除文件、重启服务等高风险操作才需确认。推荐日常使用。",
|
||||
"examples": "cat/grep/df 自动 · rm/reboot 确认"
|
||||
},
|
||||
"permissive": {
|
||||
"label": "宽松",
|
||||
"short": "仅拦截危险",
|
||||
"desc": "除 rm -rf /、mkfs 等危险命令硬性拦截外,其余全部自动执行。仅适合隔离测试环境。",
|
||||
"examples": "rm -rf / 拦截 · 其余自动"
|
||||
}
|
||||
},
|
||||
"tips": {
|
||||
"host": {
|
||||
"llmTitle": "LLM + 工具层",
|
||||
"llmDesc": "OpenAI 兼容 LLM;工具含 ssh_execute(远程执行)与 analyze_output(输出分析)。",
|
||||
"shieldTitle": "安全 + Prompt",
|
||||
"shieldDesc": "五级风险评估 + 三级安全模式;危险命令硬性拒绝,高危命令弹框确认。",
|
||||
"brainTitle": "ReAct 核心引擎",
|
||||
"brainDesc": "思考→行动→观察循环(最多 16 轮),失败换法重试,循环检测防死循环。",
|
||||
"dbTitle": "自进化引擎",
|
||||
"dbDesc": "四层记忆异步学习,同类任务自动注入历史经验,越用越聪明。"
|
||||
},
|
||||
"k8s": {
|
||||
"cloudTitle": "Kubernetes 查询",
|
||||
"cloudDesc": "kubernetes_get 统一查询节点/Pod/Deployment/事件等;pod_logs 拉取容器日志。",
|
||||
"terminalTitle": "Pod 执行",
|
||||
"terminalDesc": "选定 Pod 后可用 pod_execute 进入容器排障;kubernetes_action 执行重启/扩缩容等变更。",
|
||||
"shieldTitle": "安全 + ReAct",
|
||||
"shieldDesc": "只读工具自动执行;变更与危险命令弹框确认,最多 16 轮循环排障。",
|
||||
"dbTitle": "自进化引擎",
|
||||
"dbDesc": "按集群/命名空间分桶学习,同类 Kubernetes 排障任务越用越准。"
|
||||
}
|
||||
},
|
||||
"subtitleHost": "自然语言驱动 SSH 自主排障与部署",
|
||||
"subtitleK8s": "自然语言驱动 Kubernetes 集群查询与 Pod 排障",
|
||||
"error": {
|
||||
"connectTimeoutK8s": "连接集群超时:请检查集群凭据与大模型配置后重试。",
|
||||
"connectTimeoutHost": "连接主机超时:请检查 SSH 凭据与大模型配置后重试。",
|
||||
"connectRejectedK8s": "集群连接被拒绝:请检查集群 kubeconfig/token 配置与大模型是否启用。",
|
||||
"connectRejectedHost": "主机连接被拒绝:请确认 SSH 凭据与大模型配置后重试。",
|
||||
"selectServer": "请先选择目标主机",
|
||||
"selectCluster": "请先选择集群和命名空间",
|
||||
"notReady": "连接未就绪,请检查大模型配置或刷新页面重试",
|
||||
"notReadyRetry": "连接未就绪,请稍后重试"
|
||||
},
|
||||
"info": {
|
||||
"connectionClosed": "连接已关闭",
|
||||
"viewHistory": "查看历史记录:{{title}}({{status}})— 仅浏览,点击「继续追问」可在此基础上接着执行",
|
||||
"resuming": "正在恢复会话「{{title}}」…"
|
||||
},
|
||||
"statusDone": "已完成",
|
||||
"viewMode": {
|
||||
"readOnly": "历史记录(只读)· {{title}}",
|
||||
"continueBtn": "继续追问",
|
||||
"newTask": "新建任务"
|
||||
},
|
||||
"placeholder": {
|
||||
"viewMode": "查看模式中,点击「继续追问」或「新建任务」",
|
||||
"continueInput": "继续追问,回车发送(Shift+回车换行)",
|
||||
"inputTask": "输入任务描述,回车发送(Shift+回车换行)",
|
||||
"selectClusterNs": "请先选择集群和命名空间",
|
||||
"selectServerShort": "请先选择目标主机"
|
||||
},
|
||||
"history": {
|
||||
"loadFailed": "加载历史会话失败",
|
||||
"detailFailed": "加载会话详情失败",
|
||||
"deleteTitle": "删除会话",
|
||||
"deleteDesc": "确定删除「{{title}}」?此操作不可恢复。",
|
||||
"deleted": "已删除",
|
||||
"deleteFailed": "删除失败",
|
||||
"label": "历史",
|
||||
"newBtn": "新建",
|
||||
"collapseHistory": "收起历史",
|
||||
"selectK8s": "选择集群和命名空间后查看",
|
||||
"selectHost": "选择主机后查看",
|
||||
"noHistory": "暂无历史",
|
||||
"viewHistory": "查看历史记录",
|
||||
"rounds": "{{rounds}}轮 · {{tokens}}t",
|
||||
"statusDone": "完成",
|
||||
"statusRunning": "进行",
|
||||
"statusAborted": "中断",
|
||||
"statusError": "失败"
|
||||
},
|
||||
"empty": {
|
||||
"selectK8s": "请先选择集群和命名空间",
|
||||
"selectHost": "请先选择一台目标主机",
|
||||
"connectingK8s": "正在连接集群...",
|
||||
"connectingHost": "正在连接主机...",
|
||||
"readyK8s": "描述 Kubernetes 排障任务,例如「查看 NotReady 节点」或「分析 nginx Pod 重启原因」",
|
||||
"readyHost": "描述你要执行的任务,例如「检查磁盘使用率并清理大文件」",
|
||||
"currentSafety": "当前安全模式:{{label}}({{short}})",
|
||||
"noPod": " · 未选 Pod,仅集群/NS 级查询"
|
||||
},
|
||||
"input": {
|
||||
"hint": "Shift+回车换行 · 回车发送",
|
||||
"stop": "停止",
|
||||
"execute": "执行"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breadcrumb": {
|
||||
"dashboard": "仪表盘",
|
||||
|
||||
Reference in new issue
Block a user