feat(deepseek): normalize model labels and unwrap redirect URLs

- Normalize provider_model so UI shows DeepSeek/DeepSeek-R1 instead of raw toggle text (联网搜索 etc.)
- Unwrap DeepSeek redirect URLs (chat.deepseek.com/api/redirect?url=...) to canonical sources
- Expand reasoning sections and separate reasoning search pages from answer-body citations
- Improve webpages-trigger click heuristics and auxiliary link filtering in answer rendering

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 23:37:16 +08:00
parent bc2e23cfcb
commit 4f3c39f5f5
3 changed files with 940 additions and 123 deletions
@@ -97,6 +97,40 @@ describe("deepseek adapter helpers", () => {
expect(classified.searchResults).toHaveLength(1);
});
it("keeps reasoning search pages as citation sources separate from answer content citations", () => {
const classified = classifyDeepseekSources({
inlineLinks: [
{
url: "https://example.com/body-citation",
title: "Answer body citation",
text: "[15]",
siteName: "Body Site",
kind: "inline",
},
],
sourcePanelLinks: [],
searchPanelLinks: [
{
url: "https://example.com/reasoning-search",
title: "Reasoning searched page",
text: "浏览页面",
siteName: "Search Site",
kind: "search",
},
],
});
expect(classified.inlineCitationCandidates.map((item) => item.url)).toEqual([
"https://example.com/body-citation",
]);
expect(classified.citations.map((item) => item.url)).toEqual([
"https://example.com/reasoning-search",
]);
expect(classified.searchResults.map((item) => item.url)).toEqual([
"https://example.com/reasoning-search",
]);
});
it("retains all revealed DeepSeek search results instead of truncating after 32 items", () => {
const searchPanelLinks = Array.from({ length: 78 }, (_, index) => ({
url: `https://example.com/search-${index + 1}`,
@@ -173,6 +207,22 @@ describe("deepseek adapter helpers", () => {
}));
});
it("unwraps DeepSeek redirect URLs before storing source items", () => {
const item = buildSourceItem({
url: "https://chat.deepseek.com/api/redirect?url=https%3A%2F%2Fexample.com%2Fsource%23ref",
title: "Redirected source",
text: null,
siteName: null,
kind: "search",
});
expect(item).toEqual(expect.objectContaining({
url: "https://example.com/source",
normalized_url: "https://example.com/source",
host: "example.com",
}));
});
it("does not use numeric citation markers as source titles", () => {
const item = buildSourceItem({
url: "https://example.com/report#cite-1",
@@ -217,6 +267,45 @@ describe("deepseek adapter helpers", () => {
expect(isDeepSeekWebpagesTriggerText("搜索到 39 个网页")).toBe(false);
});
it("normalizes DeepSeek model labels without mistaking search toggles for models", () => {
const snapshot = {
url: "https://chat.deepseek.com/",
title: "DeepSeek",
loginRequired: false,
loginReason: null,
challengeRequired: false,
challengeReason: null,
busy: false,
busySignals: [],
composerValue: null,
sendDisabled: false,
answer: "Final answer",
answerText: "Final answer",
reasoning: null,
signature: "sig-1",
currentModelLabel: "深度思考 (R1)",
providerRequestID: null,
requestID: null,
inlineLinks: [],
sourcePanelLinks: [],
searchPanelLinks: [],
};
const sources = {
citations: [],
inlineCitationCandidates: [],
searchResults: [],
};
const r1Payload = buildDeepSeekPayload(snapshot, sources, "Final answer");
const searchPayload = buildDeepSeekPayload({
...snapshot,
currentModelLabel: "联网搜索",
}, sources, "Final answer");
expect(r1Payload.provider_model).toBe("DeepSeek-R1");
expect(searchPayload.provider_model).toBeNull();
});
it("builds json-safe payloads with null placeholders instead of undefined", () => {
const payload = buildDeepSeekPayload({
url: "https://chat.deepseek.com/",
File diff suppressed because it is too large Load Diff