feat: ship v0.4.0 with interactive writing mode
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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: FAIL(version 仍为 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: 单元全部 PASS;E2E 全部 PASS(LM Studio 运行中)
|
||||
|
||||
- [ ] **Step 4: Commit + tag(可选)**
|
||||
- [x] **Step 4: Commit + tag(可选)**
|
||||
|
||||
```bash
|
||||
git commit -m "chore: release v0.4.0 with interactive writing mode"
|
||||
|
||||
Reference in New Issue
Block a user