feat: ship v0.4.0 with interactive writing mode

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 17:46:16 +08:00
parent 0a054606fe
commit a8e0ba9ac9
39 changed files with 2113 additions and 89 deletions
@@ -1,6 +1,6 @@
# 笔临 P4 交互式写作 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.
> **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 (`- [x]`) syntax for tracking.
**Goal:** 交付 v0.4.0:实装交互式写作全流程(剧情走向 → 场景生成 → 命名暂停 → 微调 → 成章落盘),schema v4 持久化,工程项含 `test-results/` gitignore 与 LM Studio 测试超时调整。
@@ -59,13 +59,13 @@
- Modify: `tests/main/ai-client.test.ts`
- Modify: `e2e/ai-chat.spec.ts`
- [ ] **Step 1: `.gitignore` 追加**
- [x] **Step 1: `.gitignore` 追加**
```
test-results/
```
- [ ] **Step 2: Playwright 全局超时**
- [x] **Step 2: Playwright 全局超时**
`playwright.config.ts`
@@ -78,13 +78,13 @@ export default defineConfig({
})
```
- [ ] **Step 3: 现有 AI 用例超时上调(可选但推荐)**
- [x] **Step 3: 现有 AI 用例超时上调(可选但推荐)**
`tests/main/ai-client.test.ts``}, 180_000)`
`e2e/ai-chat.spec.ts`AI 用例 `test.setTimeout(180_000)`;助手 `expect` 保持或升至 `120_000`
- [ ] **Step 4: 验证**
- [x] **Step 4: 验证**
```bash
npm run test
@@ -92,7 +92,7 @@ npm run test
Expected: 全部 PASS(需 LM Studio 运行)
- [ ] **Step 5: Commit**
- [x] **Step 5: Commit**
```bash
git add .gitignore playwright.config.ts tests/main/ai-client.test.ts e2e/ai-chat.spec.ts
@@ -110,7 +110,7 @@ git commit -m "chore: ignore test-results and raise AI test timeouts"
- Modify: `src/shared/ipc-channels.ts`
- Create: `tests/main/migrate-v4.test.ts`
- [ ] **Step 1: 写迁移失败测试**
- [x] **Step 1: 写迁移失败测试**
`tests/main/migrate-v4.test.ts`
@@ -153,7 +153,7 @@ describe('migrate v4', () => {
})
```
- [ ] **Step 2: 运行确认 FAIL**
- [x] **Step 2: 运行确认 FAIL**
```bash
npm run test -- tests/main/migrate-v4.test.ts
@@ -161,11 +161,11 @@ npm run test -- tests/main/migrate-v4.test.ts
Expected: FAILversion 仍为 3 或表不存在)
- [ ] **Step 3: 创建 `schema-v4.sql`**
- [x] **Step 3: 创建 `schema-v4.sql`**
内容同 spec §2.3`chapters.origin``interactive_flows``interactive_scenes` 及索引)。
- [ ] **Step 4: 更新 `migrate.ts`**
- [x] **Step 4: 更新 `migrate.ts`**
```typescript
import schemaV4 from './schema-v4.sql?raw'
@@ -184,13 +184,13 @@ if (current < 4) {
}
```
- [ ] **Step 5: `types.ts` 追加类型**
- [x] **Step 5: `types.ts` 追加类型**
按 spec §3 追加:`InteractiveStep``InteractiveFlowStatus``ChapterOrigin``PlotOption``NamingPause``PlotSelection``InteractiveScene``InteractiveFlow``InteractiveGateResult``InteractiveStreamChunkEvent`
`Chapter` 接口增加 `origin?: ChapterOrigin`
- [ ] **Step 6: `ipc-channels.ts` 追加**
- [x] **Step 6: `ipc-channels.ts` 追加**
```typescript
INTERACTIVE_CHECK_GATE: 'interactive:checkGate',
@@ -208,13 +208,13 @@ INTERACTIVE_ABORT: 'interactive:abort',
INTERACTIVE_STREAM_CHUNK: 'interactive:streamChunk',
```
- [ ] **Step 7: 运行测试 PASS**
- [x] **Step 7: 运行测试 PASS**
```bash
npm run test -- tests/main/migrate-v4.test.ts
```
- [ ] **Step 8: Commit**
- [x] **Step 8: Commit**
```bash
git add src/main/db/schema-v4.sql src/main/db/migrate.ts src/shared/types.ts src/shared/ipc-channels.ts tests/main/migrate-v4.test.ts
@@ -229,7 +229,7 @@ git commit -m "feat: add schema v4 and interactive shared types"
- Create: `src/main/db/repositories/interactive.repo.ts`
- Modify: `src/main/services/book-registry.ts`
- [ ] **Step 1: 实现 `InteractiveRepository`**
- [x] **Step 1: 实现 `InteractiveRepository`**
```typescript
import { randomUUID } from 'crypto'
@@ -358,7 +358,7 @@ export class InteractiveRepository {
}
```
- [ ] **Step 2: `book-registry.ts` 注册**
- [x] **Step 2: `book-registry.ts` 注册**
```typescript
import { InteractiveRepository } from '../db/repositories/interactive.repo'
@@ -368,7 +368,7 @@ getInteractiveRepo(bookId: string): InteractiveRepository {
}
```
- [ ] **Step 3: Commit**
- [x] **Step 3: Commit**
```bash
git add src/main/db/repositories/interactive.repo.ts src/main/services/book-registry.ts
@@ -385,7 +385,7 @@ git commit -m "feat: add InteractiveRepository for flow and scene persistence"
- Modify: `src/main/db/repositories/chapter.repo.ts`
- Modify: `src/main/services/word-count.ts`(若需导出 `stripHtml`,或从 naming-check 复用)
- [ ] **Step 1: 写门槛失败测试**
- [x] **Step 1: 写门槛失败测试**
`tests/main/interactive-gate.test.ts`
@@ -427,7 +427,7 @@ describe('checkInteractiveGate', () => {
})
```
- [ ] **Step 2: 实现 `interactive-gate.service.ts`**
- [x] **Step 2: 实现 `interactive-gate.service.ts`**
```typescript
import type { InteractiveGateResult } from '../../shared/types'
@@ -454,7 +454,7 @@ export function checkInteractiveGate(db: SqliteDb): InteractiveGateResult {
(实现时可用 `stripHtml` 遍历章节列表,与 spec 语义一致即可。)
- [ ] **Step 3: `chapter.repo.ts` 支持 `origin`**
- [x] **Step 3: `chapter.repo.ts` 支持 `origin`**
`mapRow` 增加 `origin: (row.origin as ChapterOrigin) ?? 'manual'`
@@ -462,7 +462,7 @@ export function checkInteractiveGate(db: SqliteDb): InteractiveGateResult {
INSERT 增加 `origin` 列。
- [ ] **Step 4: 运行 PASS + Commit**
- [x] **Step 4: 运行 PASS + Commit**
```bash
npm run test -- tests/main/interactive-gate.test.ts
@@ -478,7 +478,7 @@ git commit -m "feat: add interactive gate check and chapter origin column"
- Create: `src/main/services/interactive-prompt-builder.service.ts`
- Create: `tests/main/interactive-parse.test.ts`
- [ ] **Step 1: 解析单元测试(无 AI)**
- [x] **Step 1: 解析单元测试(无 AI)**
`tests/main/interactive-parse.test.ts`
@@ -502,7 +502,7 @@ describe('interactive-parse', () => {
})
```
- [ ] **Step 2: 实现 `interactive-parse.service.ts`**
- [x] **Step 2: 实现 `interactive-parse.service.ts`**
```typescript
import type { NamingPause, PlotOption } from '../../shared/types'
@@ -553,7 +553,7 @@ export function tryParseNamingFromStream(buffer: string): NamingPause | null {
}
```
- [ ] **Step 3: 实现 `interactive-prompt-builder.service.ts`**
- [x] **Step 3: 实现 `interactive-prompt-builder.service.ts`**
导出函数:
@@ -564,7 +564,7 @@ export function tryParseNamingFromStream(buffer: string): NamingPause | null {
System prompt 明确要求 JSON 格式或 HTML 段落;走向步要求恰好 3 条。
- [ ] **Step 4: 运行 PASS + Commit**
- [x] **Step 4: 运行 PASS + Commit**
```bash
npm run test -- tests/main/interactive-parse.test.ts
@@ -579,7 +579,7 @@ git commit -m "feat: add interactive prompt builder and JSON parsers"
- Create: `src/main/services/interactive-writing.service.ts`
- Create: `tests/main/interactive-finish.test.ts`
- [ ] **Step 1: 写无章 AI 的 finish 测试**
- [x] **Step 1: 写无章 AI 的 finish 测试**
`tests/main/interactive-finish.test.ts`IT-INT-04,无 LM Studio):
@@ -633,7 +633,7 @@ describe('InteractiveWritingService.finishChapter', () => {
})
```
- [ ] **Step 2: 实现 `InteractiveWritingService`**
- [x] **Step 2: 实现 `InteractiveWritingService`**
构造函数注入 `db` + `AiClientService`(或窄接口 `{ chat, chatStream }`)。
@@ -656,13 +656,13 @@ finishChapter(flowId: string, volumeId: string, title?: string): { chapterId: st
其余方法按 spec 步骤机实现;`suggestPlots``aiClient.chat` + `parsePlotsJson`(失败重试 1 次);`generateScene` 流式累积 buffer,开头检测 `tryParseNamingFromStream` → 写 `naming_pending_json` 并切 `naming_pause`
- [ ] **Step 3: 运行 PASS**
- [x] **Step 3: 运行 PASS**
```bash
npm run test -- tests/main/interactive-finish.test.ts
```
- [ ] **Step 4: Commit**
- [x] **Step 4: Commit**
```bash
git commit -m "feat: add InteractiveWritingService with finishChapter"
@@ -678,7 +678,7 @@ git commit -m "feat: add InteractiveWritingService with finishChapter"
- Modify: `src/preload/index.ts`
- Modify: `src/shared/electron-api.d.ts`
- [ ] **Step 1: 实现 `registerInteractiveHandlers`**
- [x] **Step 1: 实现 `registerInteractiveHandlers`**
模式参照 `ai.handler.ts`
@@ -687,7 +687,7 @@ git commit -m "feat: add InteractiveWritingService with finishChapter"
- `generateScene` / `refineScene` / `resolveNaming`:注册 `activeInteractive` AbortController Map;流式时 `win.webContents.send(IPC.INTERACTIVE_STREAM_CHUNK, payload)`
- `finishChapter`:需 `{ bookId, flowId, volumeId, title? }`
- [ ] **Step 2: `register.ts` 注册**
- [x] **Step 2: `register.ts` 注册**
```typescript
import { registerInteractiveHandlers } from './handlers/interactive.handler'
@@ -695,7 +695,7 @@ import { registerInteractiveHandlers } from './handlers/interactive.handler'
registerInteractiveHandlers(books, settings, new AiClientService(() => settings.get().aiConfig))
```
- [ ] **Step 3: preload 暴露 `interactive` 命名空间**
- [x] **Step 3: preload 暴露 `interactive` 命名空间**
```typescript
interactive: {
@@ -707,7 +707,7 @@ interactive: {
}
```
- [ ] **Step 4: 手动 smoke**
- [x] **Step 4: 手动 smoke**
```bash
npm run build
@@ -715,7 +715,7 @@ npm run build
Expected: 编译无 TS 错误
- [ ] **Step 5: Commit**
- [x] **Step 5: Commit**
```bash
git commit -m "feat: wire interactive IPC handlers and preload API"
@@ -729,7 +729,7 @@ git commit -m "feat: wire interactive IPC handlers and preload API"
- Create: `tests/main/interactive-plots.test.ts`
- Create: `tests/main/interactive-scene.test.ts`
- [ ] **Step 1: IT-INT-01**
- [x] **Step 1: IT-INT-01**
```typescript
import { describe, it, expect } from 'vitest'
@@ -755,14 +755,14 @@ describe('interactive plots integration', () => {
})
```
- [ ] **Step 2: IT-INT-02 / IT-INT-03**`interactive-scene.test.ts`
- [x] **Step 2: IT-INT-02 / IT-INT-03**`interactive-scene.test.ts`
-`selectPlot`,再 `generateScene` 流式收集,断言正文长度 > 100
- 若模型返回 naming JSON,走 `resolveNaming` 后续写
超时:`240_000`
- [ ] **Step 3: 全量单元测试**
- [x] **Step 3: 全量单元测试**
```bash
npm run test
@@ -770,7 +770,7 @@ npm run test
Expected: 50+ tests PASS(需 LM Studio
- [ ] **Step 4: Commit**
- [x] **Step 4: Commit**
```bash
git commit -m "test: add interactive LM Studio integration tests"
@@ -787,7 +787,7 @@ git commit -m "test: add interactive LM Studio integration tests"
- Modify: `src/renderer/components/ai/AiPanel.tsx`
- Modify: `src/renderer/styles/layout.css`
- [ ] **Step 1: `useInteractiveStore`**
- [x] **Step 1: `useInteractiveStore`**
```typescript
import { create } from 'zustand'
@@ -832,11 +832,11 @@ export const useInteractiveStore = create<InteractiveState>((set, get) => ({
}))
```
- [ ] **Step 2: `PlotOptionCard.tsx`**
- [x] **Step 2: `PlotOptionCard.tsx`**
可点击卡片,`data-testid={`plot-option-${id}`}`,展示 label + summary。
- [ ] **Step 3: `InteractivePanel.tsx`**
- [x] **Step 3: `InteractivePanel.tsx`**
`flow.step` 条件渲染;关键 testid
@@ -850,7 +850,7 @@ export const useInteractiveStore = create<InteractiveState>((set, get) => ({
- `interactive-accept-scene`
- `interactive-finish-chapter`
- [ ] **Step 4: 修改 `AiPanel.tsx`**
- [x] **Step 4: 修改 `AiPanel.tsx`**
```typescript
import { InteractivePanel } from '@renderer/components/ai/InteractivePanel'
@@ -882,11 +882,11 @@ const handleModeClick = (mode: AiWritingMode): void => {
模式 Tab`interactive``!gateEligible``disabled` + `title={t('interactive.gateTooltip')}`
- [ ] **Step 5: CSS**
- [x] **Step 5: CSS**
`layout.css` 追加 `.interactive-panel``.plot-option-card``.naming-options``.interactive-step-bar`(参考 `ui_pc.html` `.plot-opt`)。
- [ ] **Step 6: Commit**
- [x] **Step 6: Commit**
```bash
git commit -m "feat: add InteractivePanel UI and wire AiPanel mode switch"
@@ -901,11 +901,11 @@ git commit -m "feat: add InteractivePanel UI and wire AiPanel mode switch"
- Modify: `public/locales/en/translation.json`
- Modify: `src/renderer/stores/useInteractiveStore.ts`finish 后导航)
- [ ] **Step 1: 追加文案键**
- [x] **Step 1: 追加文案键**
`interactive.gateBlocked``interactive.gateTooltip``interactive.step.context``interactive.step.plot``interactive.step.scene``interactive.step.review``interactive.start``interactive.acceptScene``interactive.finishChapter``interactive.chapterCreated``interactive.namingTitle``interactive.refinePlaceholder`
- [ ] **Step 2: `finishChapter` 成功后**
- [x] **Step 2: `finishChapter` 成功后**
```typescript
import { useBookStore } from '@renderer/stores/useBookStore'
@@ -922,7 +922,7 @@ useAppStore.getState().setView('editor')
showToast(t('interactive.chapterCreated'))
```
- [ ] **Step 3: Commit**
- [x] **Step 3: Commit**
```bash
git commit -m "feat: add interactive i18n and chapter navigation after finish"
@@ -935,7 +935,7 @@ git commit -m "feat: add interactive i18n and chapter navigation after finish"
**Files:**
- Create: `e2e/interactive-writing.spec.ts`
- [ ] **Step 1: E2E-INT-02(门槛,无 AI**
- [x] **Step 1: E2E-INT-02(门槛,无 AI**
```typescript
test('E2E-INT-02: interactive tab disabled on empty book', async () => {
@@ -945,7 +945,7 @@ test('E2E-INT-02: interactive tab disabled on empty book', async () => {
})
```
- [ ] **Step 2: E2E-INT-01(完整流程,300s**
- [x] **Step 2: E2E-INT-01(完整流程,300s**
```typescript
test('E2E-INT-01: interactive flow creates chapter', async () => {
@@ -963,17 +963,17 @@ test('E2E-INT-01: interactive flow creates chapter', async () => {
})
```
- [ ] **Step 3: E2E-INT-03(重启恢复,300s**
- [x] **Step 3: E2E-INT-03(重启恢复,300s**
进入交互 mid-flow → 关闭 app → 重开 → `getFlow` 恢复步骤 UI。
- [ ] **Step 4: 运行 E2E**
- [x] **Step 4: 运行 E2E**
```bash
npm run test:e2e -- e2e/interactive-writing.spec.ts
```
- [ ] **Step 5: Commit**
- [x] **Step 5: Commit**
```bash
git commit -m "test: add interactive writing E2E specs"
@@ -989,11 +989,11 @@ git commit -m "test: add interactive writing E2E specs"
- Modify: `src/renderer/components/settings/SettingsPage.tsx`about 版本号)
- Modify: `docs/superpowers/plans/2026-07-08-bilin-p4-interactive.md`(勾选完成项)
- [ ] **Step 1: 版本号 → `0.4.0`**
- [x] **Step 1: 版本号 → `0.4.0`**
- [ ] **Step 2: README 功能概览追加 P4 交互写作**
- [x] **Step 2: README 功能概览追加 P4 交互写作**
- [ ] **Step 3: 全量测试**
- [x] **Step 3: 全量测试**
```bash
npm run test
@@ -1003,7 +1003,7 @@ npm run test:e2e
Expected: 单元全部 PASSE2E 全部 PASSLM Studio 运行中)
- [ ] **Step 4: Commit + tag(可选)**
- [x] **Step 4: Commit + tag(可选)**
```bash
git commit -m "chore: release v0.4.0 with interactive writing mode"