feat: AIOps智能助手快捷操作接入真实平台数据(告警/Agent指标/仪表盘统计注入LLM上下文)
This commit is contained in:
1 parent
ae09faa780
commit
32cd3b8112
1 file changed
+106
-14
@@ -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)}
|
||||||
>
|
>
|
||||||
<action.icon className="h-4 w-4 mr-2 shrink-0" />
|
{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" />
|
||||||
|
)}
|
||||||
<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>
|
||||||
|
|||||||
Reference in new issue
Block a user