feat: AIOps智能助手快捷操作接入真实平台数据(告警/Agent指标/仪表盘统计注入LLM上下文)

This commit is contained in:
attacker committed 2026-06-18 04:06:55 +08:00
1 parent ae09faa780
commit 32cd3b8112
1 file changed
+105 -13
+105 -13
View File
@@ -23,7 +23,7 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { aiApi, type LLMConfigData, type ChatHistoryItem } from '@/services/api'; import { aiApi, alertApi, agentApi, dashboardStatsApi, type LLMConfigData, type ChatHistoryItem } from '@/services/api';
import { import {
Bot, Bot,
Send, Send,
@@ -63,12 +63,68 @@ const defaultConfig: LLMConfigData = {
enabled: false, enabled: false,
}; };
// 快捷操作(填入输入框供用户发送) // Logan.Li // ============ 快捷操作平台数据加载器(注入真实运维上下文) // Logan.Li ============
const quickActions = [ // 拉取最近告警事件,格式化为紧凑列表供 LLM 分析
{ id: 'alerts', name: '分析今日告警', icon: AlertTriangle, prompt: '帮我分析最近的告警事件,给出根因分析和处置建议' }, async function loadAlertsContext(): Promise<string> {
{ id: 'performance', name: '性能瓶颈分析', icon: Activity, prompt: '分析当前系统的性能瓶颈,找出 CPU/内存/磁盘使用率最高的服务' }, const res = await alertApi.getAlerts({ page: 1, page_size: 20 });
{ id: 'report', name: '生成运维报告', icon: FileText, prompt: '生成今日运维报告,包括资源使用、告警、发布情况' }, const alerts: any[] = (res as any)?.list || [];
{ id: 'optimize', name: '资源优化建议', icon: TrendingUp, prompt: '根据当前资源使用情况提供成本优化和容量规划建议' }, if (!alerts.length) return '(当前无告警事件)';
const lines = alerts.slice(0, 15).map((a, i) =>
`${i + 1}. [${a.severity || '未知'}|${a.status || '-'}] ${a.rule_name || '告警'} → 目标:${a.target || '-'} 值:${a.value ?? '-'} 时间:${(a.started_at || '').slice(0, 16)}`,
);
const firing = alerts.filter((a) => a.status === 'firing').length;
return `共 ${alerts.length} 条告警(firing ${firing} 条),前 ${Math.min(15, alerts.length)} 条:\n${lines.join('\n')}`;
}
// 拉取 Agent 实时主机指标,识别高负载主机
async function loadMetricsContext(): Promise<string> {
const res = await agentApi.getLatest();
const hosts: any[] = Array.isArray(res) ? res : (res as any)?.list || [];
if (!hosts.length) return '(当前无 Agent 上报数据,可能未部署监控 Agent)';
const lines = hosts.slice(0, 20).map((h, i) => {
const cpu = h.cpu_usage ?? '-';
const mem = h.mem_usage ?? '-';
return `${i + 1}. ${h.hostname || '-'} | CPU:${cpu}% | 内存:${mem}% (${h.mem_used ?? '-'}/${h.mem_total ?? '-'}GB) | 核数:${h.cpu_cores ?? '-'} | 上报:${(h.report_time || '').slice(0, 16)}`;
});
const hot = hosts.filter((h) => (h.cpu_usage ?? 0) >= 80 || (h.mem_usage ?? 0) >= 80);
const note = hot.length
? `\n⚠️ 高负载主机 ${hot.length} 台:${hot.map((h) => `${h.hostname}(CPU${h.cpu_usage}%/MEM${h.mem_usage}%)`).join('、')}`
: '';
return `Agent 监控的 ${hosts.length} 台主机实时指标:\n${lines.join('\n')}${note}`;
}
// 拉取仪表盘综合数据,生成运维报告上下文
async function loadReportContext(): Promise<string> {
const [stats, alerts, releases] = await Promise.all([
dashboardStatsApi.getStats().catch(() => null),
dashboardStatsApi.getAlerts(10).catch(() => null),
dashboardStatsApi.getReleases(10).catch(() => null),
]);
const s: any = stats || {};
const ss = s.server_stats || {}, ks = s.k8s_stats || {}, as = s.alert_stats || {}, ps = s.pipeline_stats || {}, cs = s.cost_stats || {};
const overview = `服务器:${ss.total ?? 0}(运行${ss.running ?? 0}) | K8s集群:${ks.total ?? 0} 节点:${ks.total_nodes ?? 0} Pod:${ks.total_pods ?? 0} | 告警:${as.total ?? 0}(firing ${as.firing ?? 0}) | 流水线:${ps.total ?? 0}(成功率${ps.success_rate ?? 0}%) | 本月成本:¥${cs.month_cost ?? 0}`;
const alertList: any[] = Array.isArray(alerts) ? alerts : (alerts as any)?.list || [];
const releaseList: any[] = Array.isArray(releases) ? releases : (releases as any)?.list || [];
const alertLines = alertList.slice(0, 10).map((a, i) => `${i + 1}.[${a.severity || '?'}|${a.status || '?'}] ${a.rule_name || '-'} → ${a.target || '-'}`);
const releaseLines = releaseList.slice(0, 10).map((r, i) => `${i + 1}.${r.app_name || '-'} ${r.version || ''} [${r.environment || '-'}/${r.status || '-'}] by ${r.author || '-'}`);
return `平台概览:${overview}\n\n最近告警:\n${alertLines.join('\n') || '(无)'}\n\n最近发布:\n${releaseLines.join('\n') || '(无)'}`;
}
// 快捷操作类型(contextLoader 可选,支持未来无数据加载的纯文本操作) // Logan.Li
interface QuickAction {
id: string;
name: string;
icon: typeof AlertTriangle;
prompt: string;
contextLoader?: () => Promise<string>;
}
// 快捷操作(已配置 LLM 时自动拉取真实平台数据注入上下文后发送) // Logan.Li
const quickActions: QuickAction[] = [
{ id: 'alerts', name: '分析今日告警', icon: AlertTriangle, prompt: '帮我分析以下最近的告警事件,给出根因分析和处置建议(按严重程度排序,给出具体处置步骤)', contextLoader: loadAlertsContext },
{ id: 'performance', name: '性能瓶颈分析', icon: Activity, prompt: '分析以下主机实时指标,找出 CPU/内存使用率最高的主机并给出性能优化建议', contextLoader: loadMetricsContext },
{ id: 'report', name: '生成运维报告', icon: FileText, prompt: '基于以下平台实时数据,生成一份简洁的今日运维报告,包括资源概览、告警情况和发布状态,用 Markdown 格式输出', contextLoader: loadReportContext },
{ id: 'optimize', name: '资源优化建议', icon: TrendingUp, prompt: '根据以下主机资源使用数据,提供成本优化和容量规划建议(识别闲置/过载主机,给出缩容/扩容建议)', contextLoader: loadMetricsContext },
]; ];
interface Message { interface Message {
@@ -94,6 +150,7 @@ export function AIOpsAssistant() {
const [testing, setTesting] = useState(false); const [testing, setTesting] = useState(false);
const [config, setConfig] = useState<LLMConfigData>(defaultConfig); const [config, setConfig] = useState<LLMConfigData>(defaultConfig);
const [configReady, setConfigReady] = useState(false); // 已配置且启用 const [configReady, setConfigReady] = useState(false); // 已配置且启用
const [contextLoading, setContextLoading] = useState<string | null>(null); // 快捷操作拉取平台数据中
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
// 滚动到底部 // Logan.Li // 滚动到底部 // Logan.Li
@@ -177,10 +234,8 @@ export function AIOpsAssistant() {
} }
}; };
// 发送消息(真实 LLM 对话) // Logan.Li // 发送一条消息的核心逻辑(供输入框发送和快捷操作复用) // Logan.Li
const handleSend = async () => { const sendMessage = async (text: string) => {
const text = input.trim();
if (!text || isLoading) return;
const userMsg: Message = { const userMsg: Message = {
id: Date.now().toString(), id: Date.now().toString(),
role: 'user', role: 'user',
@@ -189,7 +244,6 @@ export function AIOpsAssistant() {
}; };
const newMessages = [...messages, userMsg]; const newMessages = [...messages, userMsg];
setMessages(newMessages); setMessages(newMessages);
setInput('');
setIsLoading(true); setIsLoading(true);
try { try {
// 仅保留最近 10 条上下文避免 token 超限 // Logan.Li // 仅保留最近 10 条上下文避免 token 超限 // Logan.Li
@@ -210,6 +264,36 @@ export function AIOpsAssistant() {
} }
}; };
// 输入框发送 // Logan.Li
const handleSend = async () => {
const text = input.trim();
if (!text || isLoading) return;
setInput('');
await sendMessage(text);
};
// 快捷操作:已配置 LLM 时拉取真实平台数据注入上下文后自动发送;未配置则仅填入输入框 // Logan.Li
const handleQuickAction = async (action: QuickAction) => {
if (isLoading || contextLoading) return;
// 未配置或无数据加载器时,回退到填入输入框
if (!configReady || !action.contextLoader) {
setInput(action.prompt);
return;
}
// 拉取真实平台数据,失败时注入错误说明而非中断
setContextLoading(action.id);
let context = '';
try {
context = await action.contextLoader();
} catch {
context = '(平台数据加载失败,请稍后重试或检查后端服务)';
} finally {
setContextLoading(null);
}
const fullPrompt = `${action.prompt}\n\n【平台实时数据】\n${context}`;
await sendMessage(fullPrompt);
};
// 新对话(清空本地 + 后端持久化历史) // Logan.Li // 新对话(清空本地 + 后端持久化历史) // Logan.Li
const handleNewChat = async () => { const handleNewChat = async () => {
try { try {
@@ -268,10 +352,18 @@ export function AIOpsAssistant() {
key={action.id} key={action.id}
variant="ghost" variant="ghost"
className="w-full justify-start text-slate-300 hover:text-slate-100 hover:bg-slate-700" className="w-full justify-start text-slate-300 hover:text-slate-100 hover:bg-slate-700"
onClick={() => setInput(action.prompt)} disabled={isLoading || contextLoading !== null}
onClick={() => handleQuickAction(action)}
> >
{contextLoading === action.id ? (
<Loader2 className="h-4 w-4 mr-2 shrink-0 animate-spin" />
) : (
<action.icon className="h-4 w-4 mr-2 shrink-0" /> <action.icon className="h-4 w-4 mr-2 shrink-0" />
)}
<span className="text-sm">{action.name}</span> <span className="text-sm">{action.name}</span>
{configReady && action.contextLoader && (
<span className="ml-auto text-[10px] text-emerald-400/60 shrink-0">实时</span>
)}
</Button> </Button>
))} ))}
</CardContent> </CardContent>