import { describe, it, expect } from 'vitest' import { DatabaseSync } from 'node:sqlite' import { migrate } from '../../src/main/db/migrate' import { AiSessionRepository } from '../../src/main/db/repositories/ai-session.repo' import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo' import { KnowledgeRetrievalService } from '../../src/main/services/knowledge-retrieval.service' import { InteractiveWritingService } from '../../src/main/services/interactive-writing.service' import { AiClientService } from '../../src/main/services/ai-client.service' import { BookRegistryService } from '../../src/main/services/book-registry' import { aiContextBuilder } from '../../src/main/services/ai-context-builder.service' import { DEFAULT_AI_CONFIG, type AiContextBinding } from '../../src/shared/types' import { mkdtempSync, rmSync } from 'fs' import { join } from 'path' import { tmpdir } from 'os' const AI_TEST_CONFIG = { ...DEFAULT_AI_CONFIG, timeoutMs: 240_000, maxTokens: 4096 } const EMPTY_BINDING: AiContextBinding = { chapterIds: [], outlineIds: [], settingIds: [], inspirationIds: [], knowledgeIds: [] } describe('AI context integration', () => { it('IT-CTX-01: suggestContext returns entries when approved exist', () => { const db = new DatabaseSync(':memory:') migrate(db) new KnowledgeRepository(db).create({ type: 'fact', title: '测灵石异常', content: '石头发热', status: 'approved', importance: 4 }) const result = new KnowledgeRetrievalService(db).suggest({ topN: 5 }) expect(result.length).toBeGreaterThanOrEqual(1) expect(result[0].entry.title).toBe('测灵石异常') db.close() }) it('IT-CTX-02: build with knowledgeIds includes knowledge block in systemPrompt', () => { const userDir = mkdtempSync(join(tmpdir(), 'bilin-ctx-it-')) const registry = new BookRegistryService(userDir) const meta = registry.create({ name: '测试书', category: '玄幻' }) const bookDb = registry.getDb(meta.id) const entry = new KnowledgeRepository(bookDb).create({ type: 'foreshadow', title: '测灵石', content: '石头发热', status: 'approved' }) const result = aiContextBuilder.build( { ...EMPTY_BINDING, knowledgeIds: [entry.id] }, registry, meta.id, '作者' ) expect(result.preview.some((p) => p.kind === 'knowledge')).toBe(true) expect(result.systemPrompt).toContain('【知识·') bookDb.close() rmSync(userDir, { recursive: true, force: true }) }) it('IT-CTX-03: interactive confirm then suggestPlots uses knowledge context', async () => { const db = new DatabaseSync(':memory:') migrate(db) const session = new AiSessionRepository(db).create('t', AI_TEST_CONFIG.model) const know = new KnowledgeRepository(db).create({ type: 'foreshadow', title: '测灵石异常', content: '入门考核时测灵石发热,暗示主角特殊灵根', status: 'approved', importance: 5 }) const service = new InteractiveWritingService(db, new AiClientService(() => AI_TEST_CONFIG)) const flow = service.start(session.id) service.confirmContext(flow.id, { binding: { ...EMPTY_BINDING, knowledgeIds: [know.id] }, systemPrompt: `【知识·伏笔·测灵石异常】入门考核时测灵石发热,暗示主角特殊灵根\n\n林远参加宗门入门考核,演武场测灵石前众人围观。`, contextSummary: '0章·0纲·0设·0感·1知' }) const plots = await service.suggestPlots(flow.id) expect(plots).toHaveLength(3) db.close() }, 240_000) })