feat(monitoring): add DeepSeek monitoring end-to-end

- desktop: new DeepSeek page adapter and monitor registry
- desktop: extract generic AI auth helpers into shared module and
  skip binding when challenge signals are present
- admin-web: render DeepSeek HTML answers with inline citation anchors
  and surface reference-number badges on source cards
- server: fall back to inline_links/source_panel_links and read
  text/siteName fields when extracting DeepSeek citations

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 22:54:56 +08:00
parent f6889ecdee
commit f6e553dcc3
12 changed files with 2971 additions and 65 deletions
@@ -0,0 +1,263 @@
import { describe, expect, it } from "vitest";
import { __deepseekTestUtils } from "./deepseek";
import { getMonitorAdapter } from "./index";
const {
extractQuestionText,
buildSourceItem,
buildDeepSeekPayload,
detectDeepSeekBusySignals,
dedupeSourceItems,
classifyDeepseekSources,
mergeDeepSeekRawSourceLinks,
isObservationComplete,
isDeepSeekToggleSelected,
isDeepSeekWebpagesTriggerText,
} = __deepseekTestUtils;
describe("deepseek adapter helpers", () => {
it("extracts question text from monitoring payload", () => {
expect(extractQuestionText({ question_text: "DeepSeek 怎么看 GEO" })).toBe("DeepSeek 怎么看 GEO");
});
it("dedupes sources by normalized url without hash fragments", () => {
const items = dedupeSourceItems([
{
url: "https://example.com/a#top",
title: "A",
normalized_url: "https://example.com/a#top",
},
{
url: "https://example.com/a#bottom",
title: "A later",
normalized_url: "https://example.com/a#bottom",
},
]);
expect(items).toHaveLength(1);
expect(items[0]?.normalized_url).toBe("https://example.com/a");
expect(items[0]?.title).toBe("A");
});
it("treats revealed search cards as citation sources and inline links as content citation candidates", () => {
const classified = classifyDeepseekSources({
inlineLinks: [
{
url: "https://example.com/citation",
title: "Citation",
normalized_url: "https://example.com/citation",
},
],
sourcePanelLinks: [],
searchPanelLinks: [
{
url: "https://example.com/search",
title: "Search",
normalized_url: "https://example.com/search",
},
],
});
expect(classified.citations).toHaveLength(1);
expect(classified.inlineCitationCandidates).toHaveLength(1);
expect(classified.searchResults).toHaveLength(1);
expect(classified.citations[0]?.url).toBe("https://example.com/search");
expect(classified.inlineCitationCandidates[0]?.url).toBe("https://example.com/citation");
expect(classified.searchResults[0]?.url).toBe("https://example.com/search");
});
it("enriches numeric inline citations with source metadata from revealed search cards", () => {
const classified = classifyDeepseekSources({
inlineLinks: [
{
url: "https://example.com/citation",
title: null,
text: "-1",
siteName: null,
kind: "inline",
},
],
sourcePanelLinks: [],
searchPanelLinks: [
{
url: "https://example.com/citation",
title: "DeepSeek source article",
text: "Snippet",
siteName: "Example",
kind: "search",
},
],
});
expect(classified.inlineCitationCandidates).toHaveLength(1);
expect(classified.inlineCitationCandidates[0]?.title).toBe("DeepSeek source article");
expect(classified.inlineCitationCandidates[0]?.site_name).toBe("Example");
expect(classified.citations).toHaveLength(1);
expect(classified.searchResults).toHaveLength(1);
});
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}`,
title: `Search ${index + 1}`,
text: `Snippet ${index + 1}`,
siteName: "Example",
kind: "search" as const,
}));
const classified = classifyDeepseekSources({
inlineLinks: [],
sourcePanelLinks: [],
searchPanelLinks,
});
expect(classified.citations).toHaveLength(78);
expect(classified.searchResults).toHaveLength(78);
});
it("merges revealed sidebar search links with the snapshot search links by normalized url", () => {
const merged = mergeDeepSeekRawSourceLinks(
[
{
url: "https://example.com/report#top",
title: null,
text: "Visible title",
siteName: null,
kind: "search",
},
],
[
{
url: "https://example.com/report#bottom",
title: "Merged title",
text: "Merged snippet",
siteName: "Example",
kind: "search",
},
{
url: "https://example.com/extra",
title: "Extra result",
text: "Extra snippet",
siteName: "Example",
kind: "search",
},
],
);
expect(merged).toHaveLength(2);
expect(merged[0]).toEqual(expect.objectContaining({
url: "https://example.com/report",
title: "Merged title",
text: "Visible title",
siteName: "Example",
}));
expect(merged[1]?.url).toBe("https://example.com/extra");
});
it("normalizes deepseek raw source links into monitoring source items", () => {
const item = buildSourceItem({
url: "https://example.com/report#cite-1",
title: null,
text: "DeepSeek source title",
siteName: "Example",
kind: "source",
});
expect(item).toEqual(expect.objectContaining({
url: "https://example.com/report",
normalized_url: "https://example.com/report",
title: "DeepSeek source title",
site_name: "Example",
host: "example.com",
}));
});
it("does not use numeric citation markers as source titles", () => {
const item = buildSourceItem({
url: "https://example.com/report#cite-1",
title: null,
text: "-1",
siteName: null,
kind: "inline",
});
expect(item?.title).toBeNull();
expect(item?.site_name).toBe("example.com");
});
it("treats a stable answer as complete even without citations", () => {
expect(isObservationComplete({
answer: "这是最终答案",
busy: false,
loginRequired: false,
challengeRequired: false,
signature: "sig-1",
})).toBe(true);
});
it("ignores source-logo loading placeholders when computing busy signals", () => {
expect(detectDeepSeekBusySignals({
bodyText: "已阅读 10 个网页",
descriptors: [
"site_logo_loading site_logo_fallback",
"site_logo_loading site_logo_fallback",
],
})).toEqual([]);
});
it("recognizes deepseek toggle selected state from class names", () => {
expect(isDeepSeekToggleSelected("ds-toggle-button ds-toggle-button--selected")).toBe(true);
expect(isDeepSeekToggleSelected("ds-toggle-button")).toBe(false);
});
it("only treats the bottom 网页 pill as the DeepSeek source trigger", () => {
expect(isDeepSeekWebpagesTriggerText("57 个网页")).toBe(true);
expect(isDeepSeekWebpagesTriggerText("已阅读 57 个网页")).toBe(true);
expect(isDeepSeekWebpagesTriggerText("搜索到 39 个网页")).toBe(false);
});
it("builds json-safe payloads with null placeholders instead of undefined", () => {
const payload = buildDeepSeekPayload({
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: null,
providerRequestID: null,
requestID: null,
inlineLinks: [],
sourcePanelLinks: [],
searchPanelLinks: [],
}, {
citations: [],
inlineCitationCandidates: [],
searchResults: [],
}, "Final answer");
expect(payload.provider_model).toBeNull();
expect(payload.provider_request_id).toBeNull();
expect(payload.request_id).toBeNull();
expect((payload.raw_response_json as Record<string, unknown>).provider_model).toBeNull();
expect((payload.raw_response_json as Record<string, unknown>).provider_request_id).toBeNull();
expect((payload.raw_response_json as Record<string, unknown>).request_id).toBeNull();
expect((payload.raw_response_json as Record<string, unknown>).signature).toBe("sig-1");
expect((payload.raw_response_json as Record<string, unknown>).inline_citation_candidates).toEqual([]);
});
});
describe("monitor adapter registry", () => {
it("registers deepseek as a monitor adapter", () => {
expect(getMonitorAdapter("deepseek")?.provider).toBe("deepseek");
});
});
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
export * from "./base";
export * from "./deepseek";
export * from "./doubao";
export * from "./kimi";
export * from "./qwen";
@@ -6,3 +7,24 @@ export * from "./toutiao";
export * from "./wenxin";
export * from "./yuanbao";
export * from "./zhihu";
import { deepseekAdapter } from "./deepseek";
import { doubaoAdapter } from "./doubao";
import { kimiAdapter } from "./kimi";
import { qwenAdapter } from "./qwen";
import { wenxinAdapter } from "./wenxin";
import { yuanbaoAdapter } from "./yuanbao";
import type { MonitorAdapter } from "./base";
const monitorAdapterRegistry = new Map<string, MonitorAdapter>([
[deepseekAdapter.provider, deepseekAdapter],
[doubaoAdapter.provider, doubaoAdapter],
[kimiAdapter.provider, kimiAdapter],
[qwenAdapter.provider, qwenAdapter],
[wenxinAdapter.provider, wenxinAdapter],
[yuanbaoAdapter.provider, yuanbaoAdapter],
]);
export function getMonitorAdapter(platform: string): MonitorAdapter | null {
return monitorAdapterRegistry.get(platform) ?? null;
}