fix(llm): 测试连接校验模型确有回复,并按配置的收发模式测

测试连接此前只判 err:请求通了但模型一个字不吐(思考烧光预算、正文被
安全策略吞掉、兼容层丢 content)照样报成功,而这种配置在会话里就是不
回话——正是「测试成功、对话不成功」的来源。

- TestConnection 返回回复文本,空回复直接判失败
- 回复回传前端,成功 toast 里展示,测试通过有据可查
- 测试连接改用该配置真实的收发模式:此前恒走流式(ConfigFrom 默认
  Stream=true),选了非流式的配置测的是另一条通道;请求省略 streaming
  时仍默认流式,老客户端不受影响

Closes #65
This commit is contained in:
Autumn-27 committed 2026-08-27 11:14:48 -04:00
1 parent 68a221eda1
commit 7b1f7e141b
5 files changed
+62 -11

No files matched your search

+16 -2
View File
@@ -369,8 +369,22 @@ function ProfileSheet({
try {
// 用配置实际会跑的思考参数来测,这样不支持该字段的模型在这里就失败,
// 而不是等到跑任务时才炸。传 profile id:Key 输入框留空时用已存的 Key。
const r = await api.testLLM(format, model, baseUrl, apiKey, proxy, toStore(thinkingType), toStore(effort), profileId);
if (r.ok) toast.success(`连接成功 · ${r.latency_ms ?? "?"}ms · ${r.model ?? model}`);
const r = await api.testLLM(
format,
model,
baseUrl,
apiKey,
proxy,
toStore(thinkingType),
toStore(effort),
profileId,
streaming,
);
// 回复内容一并展示:看得见模型确实说了话,才算和会话里跑通是一回事。
if (r.ok)
toast.success(`连接成功 · ${r.latency_ms ?? "?"}ms · ${r.model ?? model}`, {
description: r.reply ? `回复:${r.reply}` : undefined,
});
else toast.error(`连接失败:${r.error ?? "未知"}`);
} catch (e) {
toast.error(`测试出错:${(e as Error).message}`);
+4 -1
View File
@@ -646,8 +646,10 @@ export const api = {
thinking_type = "",
reasoning_effort = "",
profile_id?: number,
streaming = true, // 用该配置真实的收发模式来测,别让"流式能通、非流式不通"漏到会话里
) =>
post<{ ok: boolean; error?: string; latency_ms?: number; model?: string }>("/llm/test", {
// reply = 模型实际回复(已截断);一个字都不回的配置后端直接判失败
post<{ ok: boolean; error?: string; latency_ms?: number; model?: string; reply?: string }>("/llm/test", {
provider,
model,
base_url,
@@ -656,6 +658,7 @@ export const api = {
thinking_type,
reasoning_effort,
profile_id,
streaming,
}),
llmProfiles: () => get<{ profiles: LLMProfile[] }>("/llm/profiles").then((r) => arr(r.profiles)),
saveLLMProfile: (p: {
+2 -1
View File
@@ -1155,7 +1155,8 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
}
if (path === "/llm" && m === "GET") return D.llmConfig;
if (path === "/llm" && m === "POST") return { ok: true };
if (path === "/llm/test") return { ok: true, latency_ms: 128, model: String(b.model ?? "claude-opus-4-8") };
if (path === "/llm/test")
return { ok: true, latency_ms: 128, model: String(b.model ?? "claude-opus-4-8"), reply: "OK" };
if (path === "/llm/profiles" && m === "GET") return { profiles: D.llmProfiles };
if (path === "/llm/profiles" && m === "POST") return { id: Number(b.id) || 3 };
if (path === "/llm/profiles/active") return { ok: true };