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
@@ -2375,8 +2375,8 @@ func extractMonitoringSourceItems(value interface{}) []MonitoringSourceItem {
case map[string]interface{}:
entry := MonitoringSourceItem{
URL: firstString(typed, "url", "cited_url", "link"),
Title: optionalString(firstString(typed, "title", "cited_title", "name")),
SiteName: optionalString(firstString(typed, "site_name")),
Title: optionalString(firstString(typed, "title", "cited_title", "name", "text")),
SiteName: optionalString(firstString(typed, "site_name", "siteName")),
SiteKey: optionalString(firstString(typed, "site_key")),
NormalizedURL: optionalString(firstString(typed, "normalized_url")),
Host: optionalString(firstString(typed, "host")),
@@ -68,6 +68,30 @@ func TestBuildMonitoringRawPayloadQwenIncludesSearchResultsInCitationInputs(t *t
}
}
func TestBuildMonitoringRawPayloadDeepSeekIncludesSearchResultsInCitationInputs(t *testing.T) {
searchTitle := "搜索网页结果"
req := MonitoringTaskResultRequest{
Status: "succeeded",
SearchResults: []MonitoringSourceItem{
{
URL: "https://example.com/search",
Title: &searchTitle,
},
},
}
_, sourceInputs := buildMonitoringRawPayload("deepseek", req)
if len(sourceInputs) != 1 {
t.Fatalf("buildMonitoringRawPayload() sourceInputs len = %d, want 1", len(sourceInputs))
}
for _, item := range sourceInputs {
if item.URL != "https://example.com/search" {
t.Fatalf("buildMonitoringRawPayload() source url = %q, want search url", item.URL)
}
}
}
func TestExtractMonitoringInlineCitationsFromRawResponseKimi(t *testing.T) {
raw := []byte(`{
"inline_citation_candidates": [
@@ -100,3 +124,34 @@ func TestExtractMonitoringInlineCitationsFromRawResponseKimi(t *testing.T) {
t.Fatalf("extractMonitoringInlineCitationsFromRawResponse() second url = %q, want https://example.com/b", items[1].URL)
}
}
func TestExtractMonitoringInlineCitationsFromRawResponseDeepSeekFallsBackToInlineLinks(t *testing.T) {
raw := []byte(`{
"inline_links": [
{
"url": "https://example.com/a#cite-1",
"text": "-1",
"siteName": "示例站点"
},
{
"url": "https://example.com/b",
"title": "文章 B",
"siteName": "另一个站点"
}
]
}`)
items := extractMonitoringInlineCitationsFromRawResponse(raw, "deepseek")
if len(items) != 2 {
t.Fatalf("extractMonitoringInlineCitationsFromRawResponse() len = %d, want 2", len(items))
}
if items[0].URL != "https://example.com/a" {
t.Fatalf("extractMonitoringInlineCitationsFromRawResponse() first url = %q, want https://example.com/a", items[0].URL)
}
if items[0].SiteName == nil || *items[0].SiteName != "示例站点" {
t.Fatalf("extractMonitoringInlineCitationsFromRawResponse() first site = %v, want 示例站点", items[0].SiteName)
}
if items[1].Title == nil || *items[1].Title != "文章 B" {
t.Fatalf("extractMonitoringInlineCitationsFromRawResponse() second title = %v, want 文章 B", items[1].Title)
}
}
@@ -2581,6 +2581,12 @@ func extractMonitoringInlineCitationsFromRawResponse(raw []byte, platformID stri
}
items := extractMonitoringSourceItems(payload["inline_citation_candidates"])
if len(items) == 0 && normalizeMonitoringPlatformID(platformID) == "deepseek" {
items = extractMonitoringSourceItems(payload["inline_links"])
if len(items) == 0 {
items = extractMonitoringSourceItems(payload["source_panel_links"])
}
}
if len(items) == 0 && normalizeMonitoringPlatformID(platformID) == "kimi" {
items = extractMonitoringSourceItems(payload["search_results"])
}