Skip to content

Commit 3dd29ca

Browse files
committed
fix(ai): 任务句柄契约对齐 camelCase + 401/403 区分——修复精修补充永久排队中
AiTaskHandle 经 serde camelCase 序列化为 taskId,前端读 handle.task_id 恒为 undefined:事件通道被忽略、轮询缺参报错,任务实际已失败但前端永久显示排队中(EnrichPanel 另漏 taskIdRef 赋值)。401/403 拆分便于区分密钥无效与模型无权限。
1 parent 12403d5 commit 3dd29ca

5 files changed

Lines changed: 35 additions & 13 deletions

File tree

‎app/src-tauri/src/ai_balance.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,13 @@ impl AiBalanceAdapter {
103103
.map_err(|e| format!("读取余额响应失败: {}", e))?;
104104
return parse_balance(&body);
105105
}
106-
Err(ureq::Error::Status(401 | 403, _)) => {
107-
return Err("API 密钥无效或无权限(请检查设置页密钥)".to_string());
106+
// 401/403 拆分(2026-08-21 真机 unauthorized 排查):与
107+
// ai_client.rs 同口径——401=密钥无效,403=无权限/模型未开通
108+
Err(ureq::Error::Status(401, _)) => {
109+
return Err("API 密钥无效(HTTP 401)——请检查设置页密钥或环境变量 SILICONFLOW_API_KEY".to_string());
110+
}
111+
Err(ureq::Error::Status(403, _)) => {
112+
return Err("API 密钥无权限(HTTP 403)——账号未开通该模型或额度受限".to_string());
108113
}
109114
Err(ureq::Error::Status(429, _)) => {
110115
last_err = "请求过频(HTTP 429)".to_string();

‎app/src-tauri/src/ai_client.rs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,18 @@ impl AiClient {
126126
.map_err(|e| AiClientError::Network(format!("读取响应失败: {}", e)))?;
127127
return extract_content(&body);
128128
}
129-
Err(ureq::Error::Status(401 | 403, _)) => {
130-
return Err(AiClientError::Auth("API 密钥无效或无权限(请检查设置页密钥)".to_string()));
129+
// 401/403 拆分(2026-08-21 真机 unauthorized 排查):401=密钥
130+
// 无效(换密钥),403=账号无权限/模型未开通(换模型或开权限)——
131+
// 合并时用户无法区分该修密钥还是该换模型
132+
Err(ureq::Error::Status(401, _)) => {
133+
return Err(AiClientError::Auth(
134+
"API 密钥无效(HTTP 401)——请检查设置页密钥或环境变量 SILICONFLOW_API_KEY".to_string(),
135+
));
136+
}
137+
Err(ureq::Error::Status(403, _)) => {
138+
return Err(AiClientError::Auth(
139+
"API 密钥无权限(HTTP 403)——账号未开通该模型或额度受限".to_string(),
140+
));
131141
}
132142
Err(ureq::Error::Status(402, _)) => {
133143
return Err(AiClientError::Balance("账户余额不足(请充值或切换免费档模型)".to_string()));

‎app/src/components/AiRefineCard.tsx‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,10 @@ export default function AiRefineCard({ sessionId, onApplied }: { sessionId: numb
191191
}).catch(() => undefined);
192192
}
193193
setPhase("running");
194-
const handle = await invoke<{ task_id: number; state: AiTaskState }>("ai_refine_start", {
194+
// 契约修复(2026-08-21 真机"排队中"根因):Rust AiTaskHandle 为 camelCase
195+
// 序列化(taskId),此前读 handle.task_id 恒为 undefined → 事件被忽略、
196+
// 轮询缺参报错 → 前端永久"排队中"而任务实际早已失败
197+
const handle = await invoke<{ taskId: number; state: AiTaskState }>("ai_refine_start", {
195198
sessionId,
196199
authorized: true,
197200
}).catch((e) => {
@@ -200,10 +203,10 @@ export default function AiRefineCard({ sessionId, onApplied }: { sessionId: numb
200203
return null;
201204
});
202205
if (handle) {
203-
taskIdRef.current = handle.task_id;
204-
setTaskId(handle.task_id);
206+
taskIdRef.current = handle.taskId;
207+
setTaskId(handle.taskId);
205208
void handleState(handle.state);
206-
poll(handle.task_id);
209+
poll(handle.taskId);
207210
}
208211
};
209212

‎app/src/components/EnrichPanel.tsx‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ export default function EnrichPanel({ noteId, onUpdated }: { noteId: number; onU
197197
const start = async () => {
198198
setMsg("");
199199
setPhase("running");
200-
const handle = await invoke<{ task_id: number; state: AiTaskState }>("ai_enrich_start", {
200+
// 契约修复(2026-08-21 真机"排队中"根因):Rust AiTaskHandle 为 camelCase
201+
// 序列化(taskId)——此前读 handle.task_id 恒为 undefined;且本组件遗漏
202+
// taskIdRef 赋值,事件通道永远失效,一并补上(与 AiRefineCard 同款修复)
203+
const handle = await invoke<{ taskId: number; state: AiTaskState }>("ai_enrich_start", {
201204
noteId,
202205
selectedKinds: selected,
203206
authorized: true,
@@ -207,9 +210,10 @@ export default function EnrichPanel({ noteId, onUpdated }: { noteId: number; onU
207210
return null;
208211
});
209212
if (handle) {
210-
setTaskId(handle.task_id);
213+
taskIdRef.current = handle.taskId;
214+
setTaskId(handle.taskId);
211215
void handleState(handle.state);
212-
poll(handle.task_id);
216+
poll(handle.taskId);
213217
}
214218
};
215219

‎app/src/types.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,9 +696,9 @@ export type AiTaskState =
696696
| "Succeeded"
697697
| { Failed: { reason: AiTaskFailure } };
698698

699-
/** 任务句柄(Rust AiTaskHandle) */
699+
/** 任务句柄(Rust AiTaskHandle——camelCase 契约:taskId,2026-08-21 修复) */
700700
export interface AiTaskHandle {
701-
task_id: number;
701+
taskId: number;
702702
state: AiTaskState;
703703
}
704704

0 commit comments

Comments
 (0)