feat(web): 历史解卦列表、结果页与追问功能

- 合并 DivinationResultPage 和 HistoryResultPage 为统一结果页
- 重写 HistoryFollowUpPage:API 加载历史消息、SSE 流式追问、配额管理
- 追问免费且限一次,输入框 UI 对齐设计稿(圆角容器+配额徽章+圆形发送按钮)
- 结果页追问状态根据线程消息数动态判断
- 历史列表筛选改为 9 类独立类型
- 提取 historyMessageToResultData 为共享函数,新增 enqueueFollowUpRun API
- 新增 auto_awesome/search/arrow_upward 图标
- 新增三语言 [id].astro、[id]/followup.astro、divination/result.astro 页面
This commit is contained in:
ZL-Q
2026-05-10 13:59:04 +08:00
parent 654e5ce188
commit efe48f2068
23 changed files with 2119 additions and 225 deletions
+36
View File
@@ -237,3 +237,39 @@ export async function authFetch<T>(path: string, options?: RequestInit): Promise
if (res.status === 204) return undefined as T;
return res.json() as Promise<T>;
}
/**
* Like authFetch but returns raw Response for streaming (SSE, etc.)
* Does NOT throw on non-OK responses - caller must handle response.status
*/
export async function authFetchRaw(path: string, options?: RequestInit): Promise<Response> {
if (isTokenExpired()) {
await refreshAccessToken();
}
const auth = getAuth();
if (!auth) {
redirectToLogin();
throw new Error('Not authenticated');
}
const headers = jsonHeaders(options);
headers.set('Authorization', `Bearer ${auth.access_token}`);
const url = apiUrl(path);
let res = await fetch(url, { ...options, headers });
if (res.status === 401) {
await refreshAccessToken();
const refreshed = getAuth();
if (!refreshed) {
redirectToLogin();
throw new Error('Not authenticated');
}
const retryHeaders = jsonHeaders(options);
retryHeaders.set('Authorization', `Bearer ${refreshed.access_token}`);
res = await fetch(url, { ...options, headers: retryHeaders });
}
return res;
}