2026-06-08 11:56:18 +08:00
|
|
|
|
import { describe, expect, it, vi } from 'vitest'
|
2026-05-26 10:18:44 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
vi.mock('electron/main', () => ({
|
2026-05-26 10:18:44 +08:00
|
|
|
|
app: {
|
2026-06-08 11:56:18 +08:00
|
|
|
|
getPath: () => '/tmp/geo-rankly-deepseek-test',
|
|
|
|
|
|
getVersion: () => '0.0.0-test',
|
2026-05-26 10:18:44 +08:00
|
|
|
|
isPackaged: false,
|
|
|
|
|
|
on: vi.fn(),
|
|
|
|
|
|
whenReady: () => Promise.resolve(),
|
|
|
|
|
|
},
|
|
|
|
|
|
BrowserWindow: vi.fn(),
|
|
|
|
|
|
WebContentsView: vi.fn(),
|
|
|
|
|
|
ipcMain: {
|
|
|
|
|
|
handle: vi.fn(),
|
|
|
|
|
|
on: vi.fn(),
|
|
|
|
|
|
removeHandler: vi.fn(),
|
|
|
|
|
|
},
|
|
|
|
|
|
Menu: {
|
|
|
|
|
|
buildFromTemplate: vi.fn(() => ({ popup: vi.fn() })),
|
|
|
|
|
|
setApplicationMenu: vi.fn(),
|
|
|
|
|
|
},
|
|
|
|
|
|
Tray: vi.fn(),
|
|
|
|
|
|
nativeTheme: {
|
|
|
|
|
|
on: vi.fn(),
|
|
|
|
|
|
shouldUseDarkColors: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
safeStorage: {
|
|
|
|
|
|
decryptString: vi.fn(),
|
|
|
|
|
|
encryptString: vi.fn(),
|
|
|
|
|
|
isEncryptionAvailable: () => false,
|
|
|
|
|
|
},
|
|
|
|
|
|
session: {
|
|
|
|
|
|
fromPartition: vi.fn(() => ({
|
|
|
|
|
|
cookies: {
|
|
|
|
|
|
get: vi.fn(),
|
|
|
|
|
|
remove: vi.fn(),
|
|
|
|
|
|
set: vi.fn(),
|
|
|
|
|
|
},
|
|
|
|
|
|
webRequest: {
|
|
|
|
|
|
onBeforeSendHeaders: vi.fn(),
|
|
|
|
|
|
onHeadersReceived: vi.fn(),
|
|
|
|
|
|
},
|
|
|
|
|
|
})),
|
|
|
|
|
|
},
|
2026-06-08 11:56:18 +08:00
|
|
|
|
}))
|
2026-04-27 21:34:28 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
import { __deepseekTestUtils } from './deepseek'
|
|
|
|
|
|
import { getMonitorAdapter } from './index'
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
extractQuestionText,
|
|
|
|
|
|
buildSourceItem,
|
|
|
|
|
|
buildDeepSeekPayload,
|
|
|
|
|
|
detectDeepSeekBusySignals,
|
|
|
|
|
|
dedupeSourceItems,
|
|
|
|
|
|
classifyDeepseekSources,
|
|
|
|
|
|
mergeDeepSeekRawSourceLinks,
|
2026-05-26 10:18:44 +08:00
|
|
|
|
resolveDeepSeekCitationPanelLinks,
|
2026-04-23 22:54:56 +08:00
|
|
|
|
isObservationComplete,
|
|
|
|
|
|
isDeepSeekToggleSelected,
|
|
|
|
|
|
isDeepSeekWebpagesTriggerText,
|
2026-05-26 10:18:44 +08:00
|
|
|
|
isDeepSeekReasoningSearchTriggerText,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
} = __deepseekTestUtils
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
describe('deepseek adapter helpers', () => {
|
|
|
|
|
|
it('extracts question text from monitoring payload', () => {
|
|
|
|
|
|
expect(extractQuestionText({ question_text: 'DeepSeek 怎么看 GEO?' })).toBe(
|
|
|
|
|
|
'DeepSeek 怎么看 GEO?',
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('dedupes sources by normalized url without hash fragments', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const items = dedupeSourceItems([
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/a#top',
|
|
|
|
|
|
title: 'A',
|
|
|
|
|
|
normalized_url: 'https://example.com/a#top',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/a#bottom',
|
|
|
|
|
|
title: 'A later',
|
|
|
|
|
|
normalized_url: 'https://example.com/a#bottom',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
2026-06-08 11:56:18 +08:00
|
|
|
|
])
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
expect(items).toHaveLength(1)
|
|
|
|
|
|
expect(items[0]?.normalized_url).toBe('https://example.com/a')
|
|
|
|
|
|
expect(items[0]?.title).toBe('A')
|
|
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('treats revealed search cards as citation sources and inline links as content citation candidates', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const classified = classifyDeepseekSources({
|
|
|
|
|
|
inlineLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/citation',
|
|
|
|
|
|
title: 'Citation',
|
|
|
|
|
|
normalized_url: 'https://example.com/citation',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/search',
|
|
|
|
|
|
title: 'Search',
|
|
|
|
|
|
normalized_url: 'https://example.com/search',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
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')
|
|
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('uses revealed xx webpages panel links instead of stale snapshot links for citation sources', () => {
|
2026-05-26 10:18:44 +08:00
|
|
|
|
const citationPanelLinks = resolveDeepSeekCitationPanelLinks(
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://stale.example/from-snapshot',
|
|
|
|
|
|
title: 'Stale snapshot result',
|
|
|
|
|
|
text: 'Wrong result from the page snapshot',
|
|
|
|
|
|
siteName: 'Stale',
|
|
|
|
|
|
kind: 'search',
|
2026-05-26 10:18:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://right.example/from-xx-webpages',
|
|
|
|
|
|
title: 'Right side panel result',
|
|
|
|
|
|
text: 'Correct result from DeepSeek search results',
|
|
|
|
|
|
siteName: 'Right',
|
|
|
|
|
|
kind: 'search',
|
2026-05-26 10:18:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
)
|
2026-05-26 10:18:44 +08:00
|
|
|
|
const classified = classifyDeepseekSources({
|
|
|
|
|
|
inlineLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://inline.example/body-link',
|
|
|
|
|
|
title: 'Inline body link',
|
|
|
|
|
|
text: '[1]',
|
|
|
|
|
|
siteName: 'Inline',
|
|
|
|
|
|
kind: 'inline',
|
2026-05-26 10:18:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks: citationPanelLinks,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
})
|
2026-05-26 10:18:44 +08:00
|
|
|
|
|
|
|
|
|
|
expect(citationPanelLinks.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://right.example/from-xx-webpages',
|
|
|
|
|
|
])
|
2026-05-26 10:18:44 +08:00
|
|
|
|
expect(classified.citations.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://right.example/from-xx-webpages',
|
|
|
|
|
|
])
|
2026-05-26 10:18:44 +08:00
|
|
|
|
expect(classified.searchResults.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://right.example/from-xx-webpages',
|
|
|
|
|
|
])
|
2026-05-26 10:18:44 +08:00
|
|
|
|
expect(classified.inlineCitationCandidates.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://inline.example/body-link',
|
|
|
|
|
|
])
|
|
|
|
|
|
})
|
2026-05-26 10:18:44 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('falls back to snapshot search links when the revealed xx webpages panel is unavailable', () => {
|
2026-05-26 10:18:44 +08:00
|
|
|
|
const citationPanelLinks = resolveDeepSeekCitationPanelLinks(
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://snapshot.example/result',
|
|
|
|
|
|
title: 'Snapshot result',
|
|
|
|
|
|
text: 'Fallback result',
|
|
|
|
|
|
siteName: 'Snapshot',
|
|
|
|
|
|
kind: 'search',
|
2026-05-26 10:18:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
[],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
)
|
2026-05-26 10:18:44 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
expect(citationPanelLinks.map((item) => item.url)).toEqual(['https://snapshot.example/result'])
|
|
|
|
|
|
})
|
2026-05-26 10:18:44 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('enriches numeric inline citations with source metadata from revealed search cards', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const classified = classifyDeepseekSources({
|
|
|
|
|
|
inlineLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/citation',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
title: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
text: '-1',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
siteName: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
kind: 'inline',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/citation',
|
|
|
|
|
|
title: 'DeepSeek source article',
|
|
|
|
|
|
text: 'Snippet',
|
|
|
|
|
|
siteName: 'Example',
|
|
|
|
|
|
kind: 'search',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
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)
|
|
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('keeps reasoning search pages as citation sources separate from answer content citations', () => {
|
2026-04-24 23:37:16 +08:00
|
|
|
|
const classified = classifyDeepseekSources({
|
|
|
|
|
|
inlineLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/body-citation',
|
|
|
|
|
|
title: 'Answer body citation',
|
|
|
|
|
|
text: '[15]',
|
|
|
|
|
|
siteName: 'Body Site',
|
|
|
|
|
|
kind: 'inline',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks: [
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/reasoning-search',
|
|
|
|
|
|
title: 'Reasoning searched page',
|
|
|
|
|
|
text: '浏览页面',
|
|
|
|
|
|
siteName: 'Search Site',
|
|
|
|
|
|
kind: 'search',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
})
|
2026-04-24 23:37:16 +08:00
|
|
|
|
|
|
|
|
|
|
expect(classified.inlineCitationCandidates.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://example.com/body-citation',
|
|
|
|
|
|
])
|
2026-04-24 23:37:16 +08:00
|
|
|
|
expect(classified.citations.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://example.com/reasoning-search',
|
|
|
|
|
|
])
|
2026-04-24 23:37:16 +08:00
|
|
|
|
expect(classified.searchResults.map((item) => item.url)).toEqual([
|
2026-06-08 11:56:18 +08:00
|
|
|
|
'https://example.com/reasoning-search',
|
|
|
|
|
|
])
|
|
|
|
|
|
})
|
2026-04-24 23:37:16 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('retains all revealed DeepSeek search results instead of truncating after 32 items', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const searchPanelLinks = Array.from({ length: 78 }, (_, index) => ({
|
|
|
|
|
|
url: `https://example.com/search-${index + 1}`,
|
|
|
|
|
|
title: `Search ${index + 1}`,
|
|
|
|
|
|
text: `Snippet ${index + 1}`,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
siteName: 'Example',
|
|
|
|
|
|
kind: 'search' as const,
|
|
|
|
|
|
}))
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
|
|
|
|
|
const classified = classifyDeepseekSources({
|
|
|
|
|
|
inlineLinks: [],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
expect(classified.citations).toHaveLength(78)
|
|
|
|
|
|
expect(classified.searchResults).toHaveLength(78)
|
|
|
|
|
|
})
|
2026-04-23 22:54:56 +08:00
|
|
|
|
|
2026-06-08 11:56:18 +08:00
|
|
|
|
it('merges revealed sidebar search links with the snapshot search links by normalized url', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const merged = mergeDeepSeekRawSourceLinks(
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/report#top',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
title: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
text: 'Visible title',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
siteName: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
kind: 'search',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/report#bottom',
|
|
|
|
|
|
title: 'Merged title',
|
|
|
|
|
|
text: 'Merged snippet',
|
|
|
|
|
|
siteName: 'Example',
|
|
|
|
|
|
kind: 'search',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/extra',
|
|
|
|
|
|
title: 'Extra result',
|
|
|
|
|
|
text: 'Extra snippet',
|
|
|
|
|
|
siteName: 'Example',
|
|
|
|
|
|
kind: 'search',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const item = buildSourceItem({
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/report#cite-1',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
title: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
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('unwraps DeepSeek redirect URLs before storing source items', () => {
|
2026-04-24 23:37:16 +08:00
|
|
|
|
const item = buildSourceItem({
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://chat.deepseek.com/api/redirect?url=https%3A%2F%2Fexample.com%2Fsource%23ref',
|
|
|
|
|
|
title: 'Redirected source',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
text: null,
|
|
|
|
|
|
siteName: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
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', () => {
|
2026-04-23 22:54:56 +08:00
|
|
|
|
const item = buildSourceItem({
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://example.com/report#cite-1',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
title: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
text: '-1',
|
2026-04-23 22:54:56 +08:00
|
|
|
|
siteName: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
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('treats reasoning 搜索到 xx 个网页 as a separate citation-source fallback trigger', () => {
|
|
|
|
|
|
expect(isDeepSeekReasoningSearchTriggerText('搜索到 30 个网页')).toBe(true)
|
|
|
|
|
|
expect(isDeepSeekReasoningSearchTriggerText('57 个网页')).toBe(false)
|
|
|
|
|
|
expect(isDeepSeekReasoningSearchTriggerText('已阅读 57 个网页')).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('normalizes DeepSeek model labels without mistaking search toggles for models', () => {
|
2026-04-24 23:37:16 +08:00
|
|
|
|
const snapshot = {
|
2026-06-08 11:56:18 +08:00
|
|
|
|
url: 'https://chat.deepseek.com/',
|
|
|
|
|
|
title: 'DeepSeek',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
loginRequired: false,
|
|
|
|
|
|
loginReason: null,
|
|
|
|
|
|
challengeRequired: false,
|
|
|
|
|
|
challengeReason: null,
|
|
|
|
|
|
busy: false,
|
|
|
|
|
|
busySignals: [],
|
|
|
|
|
|
composerValue: null,
|
|
|
|
|
|
sendDisabled: false,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
answer: 'Final answer',
|
|
|
|
|
|
answerText: 'Final answer',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
reasoning: null,
|
2026-06-08 11:56:18 +08:00
|
|
|
|
signature: 'sig-1',
|
|
|
|
|
|
currentModelLabel: '深度思考 (R1)',
|
2026-04-24 23:37:16 +08:00
|
|
|
|
providerRequestID: null,
|
|
|
|
|
|
requestID: null,
|
|
|
|
|
|
inlineLinks: [],
|
|
|
|
|
|
sourcePanelLinks: [],
|
|
|
|
|
|
searchPanelLinks: [],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
}
|
2026-04-24 23:37:16 +08:00
|
|
|
|
const sources = {
|
|
|
|
|
|
citations: [],
|
|
|
|
|
|
inlineCitationCandidates: [],
|
|
|
|
|
|
searchResults: [],
|
2026-06-08 11:56:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const r1Payload = buildDeepSeekPayload(snapshot, sources, 'Final answer')
|
|
|
|
|
|
const searchPayload = buildDeepSeekPayload(
|
|
|
|
|
|
{
|
|
|
|
|
|
...snapshot,
|
|
|
|
|
|
currentModelLabel: '联网搜索',
|
|
|
|
|
|
},
|
|
|
|
|
|
sources,
|
|
|
|
|
|
'Final answer',
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
expect(r1Payload.provider_model).toBe('DeepSeek')
|
|
|
|
|
|
expect(searchPayload.provider_model).toBeNull()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
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')
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|