feat: AIOps智能助手支持停止生成(AbortController中止流式,保留部分内容+已停止提示)

This commit is contained in:
attacker committed 2026-06-18 07:07:01 +08:00
1 parent 5d19878b28
commit ca8ebb1634
1 file changed
+41 -2
@@ -40,6 +40,7 @@ import {
Loader2,
CheckCircle2,
Wifi,
Square,
} from 'lucide-react';
// LLM 提供商列表(OpenAI 兼容接口) // Logan.Li
@@ -133,6 +134,7 @@ interface Message {
content: string;
timestamp: string;
isError?: boolean;
isStopped?: boolean;
}
// 格式化时间为 HH:MM // Logan.Li
@@ -145,6 +147,10 @@ export function AIOpsAssistant() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
// 用于中止流式生成(点击"停止"按钮时 abort) // Logan.Li
const abortRef = useRef<AbortController | null>(null);
// 组件卸载时中止进行中的流式请求,避免 setState on unmounted // Logan.Li
useEffect(() => () => abortRef.current?.abort(), []);
const [isConfigOpen, setIsConfigOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false);
@@ -251,6 +257,9 @@ export function AIOpsAssistant() {
];
setMessages(newMessages);
setIsLoading(true);
// 创建 AbortController 以支持\"停止生成\" // Logan.Li
const controller = new AbortController();
abortRef.current = controller;
// 用于在收到第一个 chunk 前显示"思考中"占位
let firstChunkArrived = false;
try {
@@ -266,8 +275,22 @@ export function AIOpsAssistant() {
m.id === assistantId ? { ...m, content: m.content + delta } : m,
),
);
});
}, controller.signal);
} catch (e) {
// 用户主动中止:保留已生成的部分内容,不当作错误 // Logan.Li
if (controller.signal.aborted) {
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId
? {
...m,
content: m.content || '(已停止生成)',
isStopped: true,
}
: m,
),
);
} else {
const errMsg = e instanceof Error ? e.message : '对话失败';
setMessages((prev) =>
prev.map((m) =>
@@ -280,8 +303,10 @@ export function AIOpsAssistant() {
: m,
),
);
}
} finally {
setIsLoading(false);
abortRef.current = null;
}
};
@@ -485,7 +510,10 @@ export function AIOpsAssistant() {
<span className="inline-block w-2 h-4 ml-0.5 bg-purple-400 animate-pulse align-middle" />
)}
</div>
<div className="text-xs text-slate-500 mt-1">{fmtTime(message.timestamp)}</div>
<div className="text-xs text-slate-500 mt-1">
{fmtTime(message.timestamp)}
{message.isStopped && <span className="ml-2 text-slate-600">(已停止)</span>}
</div>
</div>
</div>
);
@@ -508,6 +536,17 @@ export function AIOpsAssistant() {
<Button className="bg-purple-600 hover:bg-purple-700" onClick={handleSend} disabled={isLoading || !input.trim()}>
<Send className="h-4 w-4" />
</Button>
{/* 流式生成中显示\"停止生成\"按钮,中止当前请求 // Logan.Li */}
{isLoading && (
<Button
variant="outline"
className="border-red-500/40 text-red-400 hover:bg-red-500/10"
onClick={() => abortRef.current?.abort()}
title="停止生成"
>
<Square className="h-4 w-4" />
</Button>
)}
</div>
</CardContent>
</Card>