Files
bilin/docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md
T

928 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 笔临 P6 AI 知识上下文整合 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 交付 v0.7.0:已审核知识库条目半自动推荐 + 用户勾选确认 + 注入对话/交互/自动/向导四种 AI 模式;修复写作流 `contextJson` 仅存标题摘要的问题。
**Architecture:** 新增 `KnowledgeRetrievalService``approved` 知识条目综合打分;扩展 `AiContextBinding.knowledgeIds``AiContextBuilderService` 合并知识块;`FlowContextPayload` 统一写作流上下文存储;`KnowledgeSuggestList` + `ContextEditorModal` 知识 Tab 供四种模式共用。测试 **禁止 Mock** AI 集成/E2E 打真实 LM Studio。
**Tech Stack:** Electron 36、electron-vite、React 18、TypeScript、node:sqlite、Zustand、i18next、Vitest、Playwright
**Spec:** `docs/superpowers/specs/2026-07-07-bilin-p6-ai-context-design.md`
**LM Studio 默认:** `baseUrl=http://127.0.0.1:1234``model=gemma-4-e4b-it`
---
## File Map
| 文件 | 职责 |
|------|------|
| `src/shared/types.ts` | `KnowledgeSuggestOptions``ScoredKnowledge``FlowContextPayload`;扩展 `AiContextBinding` |
| `src/shared/ipc-channels.ts` | `KNOWLEDGE_SUGGEST_CONTEXT` |
| `src/main/services/knowledge-retrieval.service.ts` | 打分 + TopN 推荐 |
| `src/main/services/ai-context-builder.service.ts` | knowledge 块 + 预算裁剪 |
| `src/main/services/flow-context.util.ts` | `buildFlowContextPayload()` 共享 |
| `src/main/services/interactive-writing.service.ts` | `readContextText``systemPrompt` |
| `src/main/db/repositories/ai-session.repo.ts` | `parseContext``knowledgeIds` |
| `src/main/ipc/handlers/knowledge.handler.ts` | `suggestContext` |
| `src/main/ipc/handlers/interactive.handler.ts` | `FlowContextPayload` |
| `src/main/ipc/handlers/auto.handler.ts` | `readContextText` 修复 |
| `src/main/ipc/handlers/wizard.handler.ts` | 同上 + preview 知识列表 |
| `src/main/ipc/handlers/ai.handler.ts` | binding 默认 `knowledgeIds: []` |
| `src/main/services/global-settings.ts` | `knowledgeAutoSuggest` / `knowledgeSuggestTopN` 默认值 |
| `src/preload/index.ts` | `knowledge.suggestContext` |
| `src/shared/electron-api.d.ts` | 类型 |
| `src/renderer/components/ai/KnowledgeSuggestList.tsx` | 共享勾选 UI |
| `src/renderer/components/ai/ContextEditorModal.tsx` | 知识 Tab + 推荐区 |
| `src/renderer/components/ai/InteractivePanel.tsx` | context_confirm 知识区 |
| `src/renderer/components/ai/AutoPanel.tsx` | goal 后知识确认 |
| `src/renderer/components/ai/WizardPanel.tsx` | wizard_context 知识区 |
| `src/renderer/lib/ai-context-summary.ts` | 摘要含知识计数 |
| `src/renderer/stores/useInteractiveStore.ts` | binding 含 knowledgeIds |
| `public/locales/zh-CN/translation.json` | P6 文案 |
| `public/locales/en/translation.json` | P6 文案 |
| `tests/main/knowledge-retrieval.test.ts` | UT-CTX-01/04 |
| `tests/main/ai-context-builder.test.ts` | UT-CTX-02/03(扩展) |
| `tests/main/flow-context.test.ts` | FlowContextPayload 单元 |
| `tests/main/ai-context-integration.test.ts` | IT-CTX-01/02/03 |
| `e2e/ai-context.spec.ts` | E2E-CTX-* |
---
## Task 1: 类型 + IPC 通道 + 默认 binding
**Files:**
- Modify: `src/shared/types.ts`
- Modify: `src/shared/ipc-channels.ts`
- Modify: `src/main/db/repositories/ai-session.repo.ts`
- [ ] **Step 1: 扩展 types**
`src/shared/types.ts` 追加:
```typescript
export interface KnowledgeSuggestOptions {
currentChapterId?: string
goalText?: string
topN?: number
}
export interface ScoredKnowledge {
entry: KnowledgeEntry
score: number
reasons: string[]
}
export interface FlowContextPayload {
binding: AiContextBinding
systemPrompt: string
contextSummary: string
}
```
扩展 `AiContextBinding`
```typescript
export interface AiContextBinding {
chapterIds: string[]
outlineIds: string[]
settingIds: string[]
inspirationIds: string[]
knowledgeIds: string[]
}
```
扩展 `ContextPreviewKind``| 'knowledge'`
扩展 `ContextPreviewItem``knowledgeType?: KnowledgeType`
扩展 `GlobalSettings`(可选,本 Task 仅类型,Task 2 实现默认值):
```typescript
knowledgeAutoSuggest: boolean
knowledgeSuggestTopN: number
```
- [ ] **Step 2: IPC 通道**
`src/shared/ipc-channels.ts`
```typescript
KNOWLEDGE_SUGGEST_CONTEXT: 'knowledge:suggestContext',
```
- [ ] **Step 3: ai-session.repo 默认 knowledgeIds**
```typescript
const EMPTY_CONTEXT: AiContextBinding = {
chapterIds: [],
outlineIds: [],
settingIds: [],
inspirationIds: [],
knowledgeIds: []
}
function parseContext(json: string): AiContextBinding {
try {
const parsed = JSON.parse(json) as Partial<AiContextBinding>
return {
chapterIds: parsed.chapterIds ?? [],
outlineIds: parsed.outlineIds ?? [],
settingIds: parsed.settingIds ?? [],
inspirationIds: parsed.inspirationIds ?? [],
knowledgeIds: parsed.knowledgeIds ?? []
}
} catch {
return { ...EMPTY_CONTEXT }
}
}
```
- [ ] **Step 4: 全局修补 EMPTY_BINDING**
搜索 `inspirationIds: []` 且无 `knowledgeIds``AiContextBinding` 字面量(`ContextEditorModal.tsx``useInteractiveStore.ts` 等),全部补上 `knowledgeIds: []`
- [ ] **Step 5: build 验证**
```bash
npm run build
```
- [ ] **Step 6: Commit**
```bash
git add src/shared/types.ts src/shared/ipc-channels.ts src/main/db/repositories/ai-session.repo.ts
git commit -m "feat: extend AiContextBinding with knowledgeIds for P6"
```
---
## Task 2: KnowledgeRetrievalService
**Files:**
- Create: `src/main/services/knowledge-retrieval.service.ts`
- Create: `tests/main/knowledge-retrieval.test.ts`
- [ ] **Step 1: UT-CTX-01 / UT-CTX-04 失败测试**
`tests/main/knowledge-retrieval.test.ts`
```typescript
import { describe, it, expect, afterEach } from 'vitest'
import { DatabaseSync } from 'node:sqlite'
import { migrate } from '../../src/main/db/migrate'
import { VolumeRepository } from '../../src/main/db/repositories/volume.repo'
import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo'
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
import { KnowledgeRetrievalService } from '../../src/main/services/knowledge-retrieval.service'
import type { SqliteDb } from '../../src/main/db/types'
describe('KnowledgeRetrievalService', () => {
let db: SqliteDb
afterEach(() => db?.close())
it('UT-CTX-01: ranks by importance + chapter proximity + goal match', () => {
db = new DatabaseSync(':memory:')
migrate(db)
const volId = new VolumeRepository(db).create('卷一', 0).id
const chapterRepo = new ChapterRepository(db)
const cur = chapterRepo.create(volId, '当前章', 5, '<p>x</p>')
const near = chapterRepo.create(volId, '近章', 4, '<p>x</p>')
chapterRepo.create(volId, '远章', 0, '<p>x</p>')
const repo = new KnowledgeRepository(db)
const low = repo.create({
type: 'fact',
title: '无关事实',
content: '普通内容',
importance: 2,
status: 'approved'
})
const high = repo.create({
type: 'foreshadow',
title: '测灵石异常',
content: '测灵石在入门考核中发热',
importance: 5,
status: 'approved',
foreshadowStatus: 'buried',
sourceChapterId: near.id
})
const service = new KnowledgeRetrievalService(db)
const result = service.suggest({
currentChapterId: cur.id,
goalText: '测灵石入门考核',
topN: 5
})
expect(result[0].entry.id).toBe(high.id)
expect(result[0].score).toBeGreaterThan(
result.find((r) => r.entry.id === low.id)!.score
)
expect(result[0].reasons.length).toBeGreaterThan(0)
})
it('UT-CTX-04: resolved foreshadow scores lower than buried', () => {
db = new DatabaseSync(':memory:')
migrate(db)
const volId = new VolumeRepository(db).create('卷一', 0).id
const chId = new ChapterRepository(db).create(volId, '章', 0).id
const repo = new KnowledgeRepository(db)
const buried = repo.update(
repo.create({
type: 'foreshadow',
title: '埋设伏笔',
importance: 3,
status: 'approved',
foreshadowStatus: 'buried',
sourceChapterId: chId
}).id,
{ status: 'approved' }
)
const resolved = repo.create({
type: 'foreshadow',
title: '已回收伏笔',
importance: 3,
status: 'approved',
foreshadowStatus: 'resolved',
sourceChapterId: chId
})
const service = new KnowledgeRetrievalService(db)
const scores = service.suggest({ currentChapterId: chId, topN: 10 })
const buriedScore = scores.find((s) => s.entry.id === buried.id)!.score
const resolvedScore = scores.find((s) => s.entry.id === resolved.id)!.score
expect(buriedScore).toBeGreaterThan(resolvedScore)
})
})
```
- [ ] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/knowledge-retrieval.test.ts
```
- [ ] **Step 3: 实现 KnowledgeRetrievalService**
`src/main/services/knowledge-retrieval.service.ts` 核心:
```typescript
import type { KnowledgeEntry, KnowledgeSuggestOptions, ScoredKnowledge } from '../../shared/types'
import type { SqliteDb } from '../db/types'
import { ChapterRepository } from '../db/repositories/chapter.repo'
import { KnowledgeRepository } from '../db/repositories/knowledge.repo'
const STOP_WORDS = new Set(['的', '了', '在', '是', '与', '和', 'the', 'a', 'an'])
function tokenize(text: string): string[] {
return text
.toLowerCase()
.split(/[\s,,。!?、;;:]+/)
.map((t) => t.trim())
.filter((t) => t.length >= 2 && !STOP_WORDS.has(t))
}
function overlapRatio(a: string, b: string): number {
const ta = new Set(tokenize(a))
const tb = tokenize(b)
if (ta.size === 0 || tb.length === 0) return 0
let hit = 0
for (const t of tb) if (ta.has(t)) hit++
return hit / tb.length
}
function chapterProximityBonus(
entry: KnowledgeEntry,
currentSort: number | null,
chapterSort: Map<string, number>
): number {
if (currentSort == null) return 0
const ids = [entry.sourceChapterId, entry.lastMentionChapterId].filter(Boolean) as string[]
if (ids.length === 0) return 0
let best = Infinity
for (const id of ids) {
const order = chapterSort.get(id)
if (order != null) best = Math.min(best, Math.abs(currentSort - order))
}
if (best === Infinity) return 0
if (best === 0) return 30
if (best <= 3) return 20
if (best <= 10) return 10
return 0
}
export class KnowledgeRetrievalService {
constructor(private db: SqliteDb) {}
suggest(opts: KnowledgeSuggestOptions = {}): ScoredKnowledge[] {
const topN = opts.topN ?? 8
const entries = new KnowledgeRepository(this.db).list({ status: 'approved' })
if (entries.length === 0) return []
const chapters = new ChapterRepository(this.db).list()
const sortMap = new Map(chapters.map((c) => [c.id, c.sortOrder]))
const currentSort = opts.currentChapterId
? (sortMap.get(opts.currentChapterId) ?? null)
: null
const goalText = opts.goalText ?? ''
const scored: ScoredKnowledge[] = entries.map((entry) => {
let score = entry.importance * 20
const reasons: string[] = []
const prox = chapterProximityBonus(entry, currentSort, sortMap)
score += prox
if (prox > 0) reasons.push('knowledge.reason.chapterRelevant')
if (entry.type === 'foreshadow') {
const fs = entry.foreshadowStatus ?? 'buried'
if (fs === 'buried') {
score += 25
reasons.push('knowledge.reason.foreshadowBuried')
} else if (fs === 'partial') score += 15
}
if (goalText) {
const text = `${entry.title} ${entry.content}`
const ratio = overlapRatio(goalText, text)
score += Math.round(ratio * 30)
if (ratio > 0.2) reasons.push('knowledge.reason.goalMatch')
}
if (entry.importance >= 4) reasons.push('knowledge.reason.highImportance')
return { entry, score, reasons: [...new Set(reasons)] }
})
return scored.sort((a, b) => b.score - a.score).slice(0, topN)
}
}
```
- [ ] **Step 4: 运行 PASS**
```bash
npm run test -- tests/main/knowledge-retrieval.test.ts
```
- [ ] **Step 5: Commit**
```bash
git add src/main/services/knowledge-retrieval.service.ts tests/main/knowledge-retrieval.test.ts
git commit -m "feat: add KnowledgeRetrievalService with scoring for context suggest"
```
---
## Task 3: 扩展 AiContextBuilder + flow-context 工具
**Files:**
- Modify: `src/main/services/ai-context-builder.service.ts`
- Create: `src/main/services/flow-context.util.ts`
- Modify: `tests/main/ai-context-builder.test.ts`
- Create: `tests/main/flow-context.test.ts`
- [ ] **Step 1: UT-CTX-02 / UT-CTX-03 测试**
`tests/main/ai-context-builder.test.ts` 追加:
```typescript
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
it('UT-CTX-02: build includes knowledge preview items', () => {
const know = new KnowledgeRepository(db)
const entry = know.create({
type: 'foreshadow',
title: '测灵石',
content: '石头发热',
status: 'approved'
})
const result = aiContextBuilder.build(
{ chapterIds: [], outlineIds: [], settingIds: [], inspirationIds: [], knowledgeIds: [entry.id] },
registry,
bookId,
'作者'
)
expect(result.preview.some((p) => p.kind === 'knowledge')).toBe(true)
expect(result.systemPrompt).toContain('【知识·伏笔】')
expect(result.systemPrompt).toContain('测灵石')
})
it('UT-CTX-03: caps knowledge section at 4000 chars', () => {
const know = new KnowledgeRepository(db)
const ids: string[] = []
for (let i = 0; i < 20; i++) {
const e = know.create({
type: 'fact',
title: `条目${i}`,
content: '长'.repeat(400),
importance: 5,
status: 'approved'
})
ids.push(e.id)
}
const result = aiContextBuilder.build(
{ chapterIds: [], outlineIds: [], settingIds: [], inspirationIds: [], knowledgeIds: ids },
registry,
bookId,
'作者'
)
const knowledgeChars = result.preview
.filter((p) => p.kind === 'knowledge')
.reduce((s, p) => s + p.charCount, 0)
expect(knowledgeChars).toBeLessThanOrEqual(4000)
})
```
`tests/main/flow-context.test.ts`
```typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { BookRegistryService } from '../../src/main/services/book-registry'
import { buildFlowContextPayload } from '../../src/main/services/flow-context.util'
import { KnowledgeRepository } from '../../src/main/db/repositories/knowledge.repo'
describe('flow-context.util', () => {
let userDir: string
let registry: BookRegistryService
let bookId: string
beforeEach(() => {
userDir = mkdtempSync(join(tmpdir(), 'bilin-flow-ctx-'))
registry = new BookRegistryService(userDir)
bookId = registry.create({ name: '书', category: '玄幻' }).id
})
afterEach(() => rmSync(userDir, { recursive: true, force: true }))
it('buildFlowContextPayload stores systemPrompt and summary', () => {
const db = registry.getDb(bookId)
const entry = new KnowledgeRepository(db).create({
type: 'fact',
title: '测试知识',
content: '内容',
status: 'approved'
})
const payload = buildFlowContextPayload(
{
chapterIds: [],
outlineIds: [],
settingIds: [],
inspirationIds: [],
knowledgeIds: [entry.id]
},
registry,
bookId,
'作者'
)
expect(payload.systemPrompt).toContain('测试知识')
expect(payload.contextSummary).toContain('1')
db.close()
})
})
```
- [ ] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/ai-context-builder.test.ts tests/main/flow-context.test.ts
```
- [ ] **Step 3: 扩展 ai-context-builder**
要点:
- `KNOWLEDGE_CHAR_LIMIT = 500``KNOWLEDGE_TOTAL_LIMIT = 4000`
- `knowledgeTypeLabel()` 映射五种类型
-`candidates` 循环后追加 knowledge 块,带 `sortKey: entry.importance * 1000`(高 important 先保留)
- 知识区单独裁剪至 4000 后再参与总 16KB 裁剪
- 所有现有 `build()` 调用 binding 需含 `knowledgeIds`(测试已补)
`src/main/services/flow-context.util.ts`
```typescript
import type { AiContextBinding, FlowContextPayload } from '../../shared/types'
import type { BookRegistryService } from './book-registry'
import { aiContextBuilder } from './ai-context-builder.service'
export function buildFlowContextPayload(
binding: AiContextBinding,
registry: BookRegistryService,
bookId: string,
penName: string
): FlowContextPayload {
const built = aiContextBuilder.build(binding, registry, bookId, penName)
const counts = { chapter: 0, outline: 0, setting: 0, inspiration: 0, knowledge: 0 }
for (const p of built.preview) {
if (p.kind in counts) counts[p.kind as keyof typeof counts]++
}
const contextSummary = `${counts.chapter}章·${counts.outline}纲·${counts.setting}设·${counts.inspiration}感·${counts.knowledge}`
return { binding, systemPrompt: built.systemPrompt, contextSummary }
}
```
- [ ] **Step 4: 运行 PASS**
```bash
npm run test -- tests/main/ai-context-builder.test.ts tests/main/flow-context.test.ts
```
- [ ] **Step 5: Commit**
```bash
git add src/main/services/ai-context-builder.service.ts src/main/services/flow-context.util.ts tests/main/
git commit -m "feat: extend AiContextBuilder with knowledge blocks and flow payload helper"
```
---
## Task 4: IPC suggestContext + preload
**Files:**
- Modify: `src/main/ipc/handlers/knowledge.handler.ts`
- Modify: `src/preload/index.ts`
- Modify: `src/shared/electron-api.d.ts`
- Modify: `src/main/services/global-settings.ts`
- Modify: `src/renderer/stores/useSettingsStore.ts`
- [ ] **Step 1: knowledge.handler 追加**
```typescript
ipcMain.handle(
IPC.KNOWLEDGE_SUGGEST_CONTEXT,
(
_e,
{ bookId, opts }: { bookId: string; opts?: KnowledgeSuggestOptions }
) => wrap(() => {
const settings = /* inject via handler param or read topN from settings */
new KnowledgeRetrievalService(registry.getDb(bookId)).suggest(opts)
})
)
```
`registerKnowledgeHandlers` 签名改为接收 `GlobalSettingsService``topN` 默认 `settings.get().knowledgeSuggestTopN ?? 8`
- [ ] **Step 2: global-settings 默认值**
```typescript
knowledgeAutoSuggest: true,
knowledgeSuggestTopN: 8,
```
`get()` merge 补默认。
- [ ] **Step 3: preload + electron-api.d.ts**
```typescript
suggestContext: (
bookId: string,
opts?: KnowledgeSuggestOptions
): Promise<IpcResult<ScoredKnowledge[]>>
```
- [ ] **Step 4: build**
```bash
npm run build
```
- [ ] **Step 5: Commit**
```bash
git commit -m "feat: add knowledge suggestContext IPC and global settings defaults"
```
---
## Task 5: 写作流 systemPrompt 修复
**Files:**
- Modify: `src/main/services/interactive-writing.service.ts`
- Modify: `src/main/ipc/handlers/interactive.handler.ts`
- Modify: `src/main/ipc/handlers/auto.handler.ts`
- Modify: `src/main/ipc/handlers/wizard.handler.ts`
- Modify: `src/main/services/wizard-writing.service.ts``buildPreview` 追加知识列表)
- [ ] **Step 1: interactive confirmContext 存 FlowContextPayload**
`interactive.handler.ts` `INTERACTIVE_CONFIRM_CONTEXT`
```typescript
const payload = buildFlowContextPayload(binding, registry, bookId, settings.get().penName)
const flow = service.confirmContext(flowId, payload)
```
`InteractiveWritingService.confirmContext` 改为:
```typescript
confirmContext(flowId: string, payload: FlowContextPayload): InteractiveFlow {
this.repo.updateStep(flowId, 'plot_suggest', {
contextJson: JSON.stringify(payload)
})
return this.repo.get(flowId)!
}
private readContextText(flowId: string): string {
try {
const parsed = JSON.parse(this.repo.getContextJson(flowId)) as FlowContextPayload
return parsed.systemPrompt || '(无上下文)'
} catch {
return '(无上下文)'
}
}
enrichFlow(flow: InteractiveFlow): InteractiveFlow {
try {
const parsed = JSON.parse(this.repo.getContextJson(flow.id)) as FlowContextPayload
return { ...flow, contextSummary: parsed.contextSummary }
} catch {
return flow
}
}
```
- [ ] **Step 2: auto.handler / wizard.handler readContextText**
替换本地 `readContextText` 为读 `systemPrompt`
```typescript
function readContextText(registry, bookId, flowId): string {
const row = registry.getDb(bookId)
.prepare('SELECT context_json FROM interactive_flows WHERE id = ?')
.get(flowId) as { context_json: string } | undefined
try {
const parsed = JSON.parse(row?.context_json ?? '{}') as FlowContextPayload
return parsed.systemPrompt ?? '(无上下文)'
} catch {
return '(无上下文)'
}
}
```
- [ ] **Step 3: wizard confirmContext 同 interactive**
`wizard.handler.ts` `WIZARD_CONFIRM_CONTEXT` 使用 `buildFlowContextPayload`
- [ ] **Step 4: buildPreview 追加知识标题**
`WizardWritingService.buildPreview` 返回前,从 `payload.binding.knowledgeIds` 查标题,append `\n将注入知识:A、B、C`
- [ ] **Step 5: auto planScenes 前自动 confirm**
`AutoPanel``planScenes` 调用前,若 flow 无 `systemPrompt`,用 session binding + 默认推荐 knowledgeIds 调 `interactive.confirmContext` 等价逻辑(可新增 `auto:confirmContext` IPC 或复用 `buildFlowContextPayload` + `setContextSummary` 扩展为存完整 payload)。
推荐:新增 `auto.handler` `AUTO_CONFIRM_CONTEXT` 与 interactive 对称。
- [ ] **Step 6: Commit**
```bash
git commit -m "fix: store full systemPrompt in writing flow contextJson for P6"
```
---
## Task 6: KnowledgeSuggestList + ContextEditorModal
**Files:**
- Create: `src/renderer/components/ai/KnowledgeSuggestList.tsx`
- Modify: `src/renderer/components/ai/ContextEditorModal.tsx`
- Modify: `src/renderer/lib/ai-context-summary.ts`
- [ ] **Step 1: KnowledgeSuggestList**
Props
```typescript
interface KnowledgeSuggestListProps {
items: ScoredKnowledge[]
selectedIds: string[]
onToggle: (id: string) => void
}
```
渲染 checkbox + title + `t(\`knowledge.type.${entry.type}\`)` + reason badges `t(reason)`。
- [ ] **Step 2: ContextEditorModal 扩展**
新增 props`goalText?: string`
打开时:
```typescript
const chapterId = useBookStore.getState().selectedChapterId ?? undefined
const suggested = await ipcCall(() =>
window.electronAPI.knowledge.suggestContext(bookId, { currentChapterId: chapterId, goalText })
)
// 默认 merge suggested ids into binding.knowledgeIds
```
新增 Tab `knowledge`;顶部推荐区 + `context-refresh-suggest`;下方 approved 全列表(`knowledge.list(bookId, { status: 'approved' })`)可勾选。
- [ ] **Step 3: formatContextSummary 含 knowledge**
```typescript
const counts = { chapter: 0, outline: 0, setting: 0, inspiration: 0, knowledge: 0 }
// ...
return t('ai.contextSummary', { ...counts })
```
更新 `ai.contextSummary` i18n 为 `{{chapters}}章·{{outlines}}纲·{{settings}}设·{{inspirations}}感·{{knowledge}}知`。
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: add KnowledgeSuggestList and knowledge tab in context editor"
```
---
## Task 7: 交互 / 自动 / 向导 Panel 集成
**Files:**
- Modify: `src/renderer/components/ai/InteractivePanel.tsx`
- Modify: `src/renderer/components/ai/AutoPanel.tsx`
- Modify: `src/renderer/components/ai/WizardPanel.tsx`
- Modify: `src/renderer/stores/useInteractiveStore.ts`
- Modify: `src/renderer/stores/useAutoStore.ts`confirmContext
- Modify: `src/renderer/components/ai/AiPanel.tsx`(传 goalText 空)
- [ ] **Step 1: InteractivePanel context_confirm**
- mount 时 `suggestContext` → state `suggested`
- 内联 `<KnowledgeSuggestList>``data-testid="interactive-knowledge-suggest"`
- 勾选同步到 session `context.knowledgeIds``ai.sessionUpdate`
- `confirmContextAndStart` 传含 `knowledgeIds` 的 binding
- [ ] **Step 2: AutoPanel**
- `goal_setup` 区域下方折叠「知识上下文」`data-testid="auto-knowledge-suggest"`
- `goalText` = `chapterGoal` + 选中大纲 description
- `planScenes` 前调用 `auto.confirmContext`Task 5 新增 IPC
- [ ] **Step 3: WizardPanel wizard_context**
- 同 Interactive 内联推荐列表 `data-testid="wizard-knowledge-suggest"`
- `handleContextClose` 传 `goalText` = goal + styleNote
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: integrate knowledge suggest into interactive auto wizard panels"
```
---
## Task 8: i18n
**Files:**
- Modify: `public/locales/zh-CN/translation.json`
- Modify: `public/locales/en/translation.json`
- [ ] **Step 1: 追加 spec §9 全部键 + `ai.contextSummary` 更新**
- [ ] **Step 2: Commit**
```bash
git commit -m "feat: add P6 AI context i18n strings"
```
---
## Task 9: 集成测试
**Files:**
- Create: `tests/main/ai-context-integration.test.ts`
- [ ] **Step 1: IT-CTX-01**
```typescript
it('IT-CTX-01: suggestContext returns entries when approved exist', () => {
// setup approved entry → suggest → length >= 1
})
```
- [ ] **Step 2: IT-CTX-02**
通过 `aiContextBuilder.build` 含 knowledgeIds 断言 `systemPrompt` 含 `【知识·`(无需 Electron IPC)。
- [ ] **Step 3: IT-CTX-03240s**
```typescript
it('IT-CTX-03: interactive confirm then suggestPlots uses knowledge context', async () => {
// setup book + knowledge + interactive flow
// confirmContext with knowledgeIds
// suggestPlots returns 3 options
}, 240_000)
```
- [ ] **Step 4: 运行**
```bash
npm run test -- tests/main/ai-context-integration.test.ts
```
- [ ] **Step 5: Commit**
```bash
git commit -m "test: add P6 AI context integration tests"
```
---
## Task 10: E2E
**Files:**
- Create: `e2e/ai-context.spec.ts`
- [ ] **Step 1: E2E-CTX-01**
```typescript
test('E2E-CTX-01: context editor knowledge tab shows suggestions', async () => {
// create book → add approved knowledge via panel → open AI context editor → context-tab-knowledge visible
})
```
- [ ] **Step 2: E2E-CTX-02**
```typescript
test('E2E-CTX-02: interactive context_confirm shows knowledge suggest', async () => {
// open interactive → interactive-knowledge-suggest visible
})
```
- [ ] **Step 3: E2E-CTX-03300s**
```typescript
test('E2E-CTX-03: chat with knowledge context gets AI reply', async () => {
test.setTimeout(300_000)
// add knowledge → select in context → send chat → non-empty reply
})
```
- [ ] **Step 4: 运行**
```bash
npm run test:e2e -- e2e/ai-context.spec.ts
```
- [ ] **Step 5: Commit**
```bash
git commit -m "test: add P6 AI context E2E specs"
```
---
## Task 11: v0.7.0 交付
**Files:**
- Modify: `package.json`
- Modify: `README.md`
- Modify: `src/renderer/components/settings/SettingsPage.tsx`about 版本号)
- Modify: `docs/superpowers/plans/2026-07-07-bilin-p6-ai-context.md`(勾选完成项)
- [ ] **Step 1: 版本号 `0.7.0`**
- [ ] **Step 2: README 追加**
```markdown
- 知识库 AI 上下文:半自动推荐 + 勾选注入四种写作模式(P6)
```
- [ ] **Step 3: 全量测试**
```bash
npm run build
npm run test
npm run test:e2e
```
- [ ] **Step 4: Commit**
```bash
git commit -m "feat: ship v0.7.0 with AI knowledge context integration"
```
---
## Spec Coverage Checklist
| Spec 要求 | Task |
|-----------|------|
| KnowledgeRetrievalService 打分 | Task 2 |
| AiContextBinding.knowledgeIds | Task 1 |
| AiContextBuilder 知识块 | Task 3 |
| FlowContextPayload / systemPrompt 修复 | Task 3, 5 |
| knowledge:suggestContext IPC | Task 4 |
| ContextEditorModal 知识 Tab | Task 6 |
| 对话静默注入 | Task 5AI_CHAT 已用 build+ Task 6 |
| 交互 context_confirm | Task 7 |
| 自动写作知识确认 | Task 5, 7 |
| 向导 wizard_context | Task 7 |
| global-settings 默认值 | Task 4 |
| UT-CTX-01~04 | Task 2, 3 |
| IT-CTX-01~03 | Task 9 |
| E2E-CTX-01~03 | Task 10 |
| v0.7.0 DoD | Task 11 |