feat: AIOps智能助手支持停止生成(AbortController中止流式,保留部分内容+已停止提示)
This commit is contained in:
1 parent
5d19878b28
commit
ca8ebb1634
1 file changed
+41
-2
@@ -40,6 +40,7 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Wifi,
|
Wifi,
|
||||||
|
Square,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// LLM 提供商列表(OpenAI 兼容接口) // Logan.Li
|
// LLM 提供商列表(OpenAI 兼容接口) // Logan.Li
|
||||||
@@ -133,6 +134,7 @@ interface Message {
|
|||||||
content: string;
|
content: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
|
isStopped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间为 HH:MM // Logan.Li
|
// 格式化时间为 HH:MM // Logan.Li
|
||||||
@@ -145,6 +147,10 @@ export function AIOpsAssistant() {
|
|||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
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 [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [testing, setTesting] = useState(false);
|
const [testing, setTesting] = useState(false);
|
||||||
@@ -251,6 +257,9 @@ export function AIOpsAssistant() {
|
|||||||
];
|
];
|
||||||
setMessages(newMessages);
|
setMessages(newMessages);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
// 创建 AbortController 以支持\"停止生成\" // Logan.Li
|
||||||
|
const controller = new AbortController();
|
||||||
|
abortRef.current = controller;
|
||||||
// 用于在收到第一个 chunk 前显示"思考中"占位
|
// 用于在收到第一个 chunk 前显示"思考中"占位
|
||||||
let firstChunkArrived = false;
|
let firstChunkArrived = false;
|
||||||
try {
|
try {
|
||||||
@@ -266,8 +275,22 @@ export function AIOpsAssistant() {
|
|||||||
m.id === assistantId ? { ...m, content: m.content + delta } : m,
|
m.id === assistantId ? { ...m, content: m.content + delta } : m,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}, controller.signal);
|
||||||
} catch (e) {
|
} 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 : '对话失败';
|
const errMsg = e instanceof Error ? e.message : '对话失败';
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
prev.map((m) =>
|
prev.map((m) =>
|
||||||
@@ -280,8 +303,10 @@ export function AIOpsAssistant() {
|
|||||||
: m,
|
: m,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
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" />
|
<span className="inline-block w-2 h-4 ml-0.5 bg-purple-400 animate-pulse align-middle" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -508,6 +536,17 @@ export function AIOpsAssistant() {
|
|||||||
<Button className="bg-purple-600 hover:bg-purple-700" onClick={handleSend} disabled={isLoading || !input.trim()}>
|
<Button className="bg-purple-600 hover:bg-purple-700" onClick={handleSend} disabled={isLoading || !input.trim()}>
|
||||||
<Send className="h-4 w-4" />
|
<Send className="h-4 w-4" />
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in new issue
Block a user