feat: ship v0.3.0 with P2.1 editor polish and P3 AI collaboration
Add chapter reorder, search replace, inspiration capture, and AI chat with context editing, settings, offline handling, and naming checks. Fix dev black screen by keeping process.env reads in the main process only. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
- Create: `tests/main/chapter-move.test.ts`
|
||||
- Create: `e2e/p21-features.spec.ts`(DRAG 用例)
|
||||
|
||||
- [ ] **Step 1: 写 chapter.move 失败测试**
|
||||
- [x] **Step 1: 写 chapter.move 失败测试**
|
||||
|
||||
`tests/main/chapter-move.test.ts`:
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('ChapterRepository.move', () => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行确认 FAIL**
|
||||
- [x] **Step 2: 运行确认 FAIL**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/chapter-move.test.ts
|
||||
@@ -104,7 +104,7 @@ npm run test -- tests/main/chapter-move.test.ts
|
||||
|
||||
Expected: FAIL `chapters.move is not a function`
|
||||
|
||||
- [ ] **Step 3: 实现 `ChapterRepository.move`**
|
||||
- [x] **Step 3: 实现 `ChapterRepository.move`**
|
||||
|
||||
```typescript
|
||||
move(chapterId: string, volumeId: string, sortOrder: number): Chapter {
|
||||
@@ -124,7 +124,7 @@ reorderVolume(volumeId: string, orderedIds: string[]): void {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: IPC `CHAPTER_MOVE`**
|
||||
- [x] **Step 4: IPC `CHAPTER_MOVE`**
|
||||
|
||||
`ipc-channels.ts` 追加 `CHAPTER_MOVE: 'chapter:move'`。
|
||||
|
||||
@@ -140,7 +140,7 @@ ipcMain.handle(
|
||||
|
||||
preload / electron-api.d.ts 暴露 `chapter.move(bookId, chapterId, volumeId, sortOrder)`。
|
||||
|
||||
- [ ] **Step 5: EditorLayout 拖拽 UI**
|
||||
- [x] **Step 5: EditorLayout 拖拽 UI**
|
||||
|
||||
章节 `.chapter-item` 增加 `draggable`;`onDragStart` 设 `chapterId`;`onDragOver` preventDefault;`onDrop` 计算新 `sortOrder` 调 IPC。
|
||||
|
||||
@@ -148,11 +148,11 @@ preload / electron-api.d.ts 暴露 `chapter.move(bookId, chapterId, volumeId, so
|
||||
|
||||
跨卷:drop 到另一卷 header → `move(chapterId, targetVolId, targetLen)`。
|
||||
|
||||
- [ ] **Step 6: E2E E2E-P21-DRAG-01**
|
||||
- [x] **Step 6: E2E E2E-P21-DRAG-01**
|
||||
|
||||
`e2e/p21-features.spec.ts`:两章 → drag 交换顺序 → 重启 → 顺序保持。
|
||||
|
||||
- [ ] **Step 7: 验证**
|
||||
- [x] **Step 7: 验证**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/chapter-move.test.ts
|
||||
@@ -176,7 +176,7 @@ git commit -am "feat: add chapter drag reorder within and across volumes"
|
||||
- Modify: `public/locales/zh-CN/translation.json`, `public/locales/en/translation.json`
|
||||
- Extend: `e2e/p21-features.spec.ts`
|
||||
|
||||
- [ ] **Step 1: SearchModal 替换折叠区**
|
||||
- [x] **Step 1: SearchModal 替换折叠区**
|
||||
|
||||
在 `search-results` 下方增加:
|
||||
|
||||
@@ -204,15 +204,15 @@ setReplacePreviews(result.previews)
|
||||
|
||||
`executeReplace`:`dryRun: false` → `refreshChapters()` + toast。
|
||||
|
||||
- [ ] **Step 2: i18n 键**
|
||||
- [x] **Step 2: i18n 键**
|
||||
|
||||
`search.replace`, `search.replacePreview`, `search.replaceExecute`, `search.replaceConfirm`, `search.replaceDone`
|
||||
|
||||
- [ ] **Step 3: E2E E2E-P21-REP-01**
|
||||
- [x] **Step 3: E2E E2E-P21-REP-01**
|
||||
|
||||
写入章节唯一词 → 搜索 → 预览 → 执行 → `editor` 含新词。
|
||||
|
||||
- [ ] **Step 4: 验证 + Commit**
|
||||
- [x] **Step 4: 验证 + Commit**
|
||||
|
||||
```bash
|
||||
npm run test:e2e -- e2e/p21-features.spec.ts
|
||||
@@ -233,7 +233,7 @@ git commit -am "feat: add global search replace preview and execute UI"
|
||||
- Modify: `src/renderer/styles/layout.css`
|
||||
- Extend: `e2e/p21-features.spec.ts`
|
||||
|
||||
- [ ] **Step 1: InspirationModal**
|
||||
- [x] **Step 1: InspirationModal**
|
||||
|
||||
对齐 `design/ui_pc.html` `#inspirationModal`:title、content、tags;保存调 `inspiration.create`;`data-testid="inspiration-modal"`。
|
||||
|
||||
@@ -251,11 +251,11 @@ if (SpeechRecognition) {
|
||||
|
||||
不可用时 `disabled` + `title={t('inspiration.voiceUnavailable')}`。
|
||||
|
||||
- [ ] **Step 2: App.tsx 改 captureInspiration**
|
||||
- [x] **Step 2: App.tsx 改 captureInspiration**
|
||||
|
||||
`captureInspiration` 快捷键 → `useAppStore.setInspirationModalOpen(true)`,不再直接 create。
|
||||
|
||||
- [ ] **Step 3: Mention 点击跳转**
|
||||
- [x] **Step 3: Mention 点击跳转**
|
||||
|
||||
`TipTapEditor` `editorProps`:
|
||||
|
||||
@@ -271,13 +271,13 @@ handleClick: (view, pos, event) => {
|
||||
|
||||
确保 `applyMention` 插入 HTML 含 `data-id`。
|
||||
|
||||
- [ ] **Step 4: 引用钉住**
|
||||
- [x] **Step 4: 引用钉住**
|
||||
|
||||
`useReferenceStore`:`pinned: boolean`, `setPinned`。
|
||||
|
||||
`ReferencePanel`:`pinned` 时关闭按钮改为取消钉住;`setOpen(false)` 若 `pinned` 则忽略。
|
||||
|
||||
- [ ] **Step 5: E2E E2E-P21-INSP-01**
|
||||
- [x] **Step 5: E2E E2E-P21-INSP-01**
|
||||
|
||||
`Control+Shift+I` → 填内容 → 保存 → `inspiration-list` 可见。
|
||||
|
||||
@@ -299,19 +299,19 @@ git commit -am "feat: add inspiration modal, mention navigation, reference pin"
|
||||
- Modify: `src/shared/ipc-channels.ts`
|
||||
- Modify: `src/main/services/global-settings.ts`
|
||||
|
||||
- [ ] **Step 1: schema-v3.sql**
|
||||
- [x] **Step 1: schema-v3.sql**
|
||||
|
||||
内容同 spec §3.1 `ai_sessions` / `ai_messages` / index。
|
||||
|
||||
- [ ] **Step 2: migrate.ts**
|
||||
- [x] **Step 2: migrate.ts**
|
||||
|
||||
`CURRENT_VERSION = 3`;`current < 3` 时 `db.exec(schemaV3)` + insert version 3。
|
||||
|
||||
- [ ] **Step 3: migrate-v3.test.ts**
|
||||
- [x] **Step 3: migrate-v3.test.ts**
|
||||
|
||||
v2 库 migrate 后 `SELECT name FROM sqlite_master` 含 `ai_sessions`。
|
||||
|
||||
- [ ] **Step 4: types.ts 扩展**
|
||||
- [x] **Step 4: types.ts 扩展**
|
||||
|
||||
```typescript
|
||||
export interface AiConfig {
|
||||
@@ -334,11 +334,11 @@ export const DEFAULT_AI_CONFIG: AiConfig = {
|
||||
|
||||
`GlobalSettings` 追加 `aiConfig`, `namingIgnoreWords: string[]`。
|
||||
|
||||
- [ ] **Step 5: global-settings defaults()**
|
||||
- [x] **Step 5: global-settings defaults()**
|
||||
|
||||
合并 `aiConfig: DEFAULT_AI_CONFIG`, `namingIgnoreWords: []`。
|
||||
|
||||
- [ ] **Step 6: 验证 + Commit**
|
||||
- [x] **Step 6: 验证 + Commit**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/migrate-v3.test.ts
|
||||
@@ -353,7 +353,7 @@ git commit -am "feat: add schema v3 and AI config types"
|
||||
- Create: `src/main/services/ai-client.service.ts`
|
||||
- Create: `tests/main/ai-client.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写真实 LM Studio 集成测试(禁止 skip)**
|
||||
- [x] **Step 1: 写真实 LM Studio 集成测试(禁止 skip)**
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest'
|
||||
@@ -372,13 +372,13 @@ describe('AiClientService', () => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行确认 FAIL**
|
||||
- [x] **Step 2: 运行确认 FAIL**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/ai-client.test.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 实现 AiClientService**
|
||||
- [x] **Step 3: 实现 AiClientService**
|
||||
|
||||
```typescript
|
||||
export interface ChatMessage {
|
||||
@@ -446,7 +446,7 @@ export class AiClientService {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 验证 PASS(需 LM Studio 运行)**
|
||||
- [x] **Step 4: 验证 PASS(需 LM Studio 运行)**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/ai-client.test.ts
|
||||
@@ -470,13 +470,13 @@ git commit -am "feat: add AiClientService with LM Studio OpenAI compatibility"
|
||||
- Modify: `src/main/services/book-registry.ts`
|
||||
- Modify: `src/preload/index.ts`, `src/shared/electron-api.d.ts`
|
||||
|
||||
- [ ] **Step 1: ai-session.repo 测试 + 实现**
|
||||
- [x] **Step 1: ai-session.repo 测试 + 实现**
|
||||
|
||||
CRUD:`list`, `create`, `update`, `delete`, `listMessages`, `addMessage`。
|
||||
|
||||
`context_json` ↔ `AiContextBinding` JSON 序列化。
|
||||
|
||||
- [ ] **Step 2: ai.handler.ts**
|
||||
- [x] **Step 2: ai.handler.ts**
|
||||
|
||||
注册 spec §4.1 全部 `ai:session*` / `ai:messageList`。
|
||||
|
||||
@@ -497,11 +497,11 @@ ipcMain.handle(IPC.AI_CHAT, async (event, { bookId, sessionId, content }) => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 3: book-registry getAiSessionRepo**
|
||||
- [x] **Step 3: book-registry getAiSessionRepo**
|
||||
|
||||
- [ ] **Step 4: preload 暴露 `window.electronAPI.ai.*`**
|
||||
- [x] **Step 4: preload 暴露 `window.electronAPI.ai.*`**
|
||||
|
||||
- [ ] **Step 5: 验证**
|
||||
- [x] **Step 5: 验证**
|
||||
|
||||
```bash
|
||||
npm run test -- tests/main/ai-session.repo.test.ts
|
||||
@@ -523,7 +523,7 @@ git commit -am "feat: add AI session repository and IPC handlers"
|
||||
- Modify: `src/main/index.ts`
|
||||
- Modify: `src/preload/index.ts`
|
||||
|
||||
- [ ] **Step 1: 改造 ai:chat 为流式**
|
||||
- [x] **Step 1: 改造 ai:chat 为流式**
|
||||
|
||||
`ai:chat` 内:
|
||||
|
||||
@@ -540,7 +540,7 @@ repo.addMessage(sessionId, 'assistant', full, assistantId)
|
||||
|
||||
`ai:abort`:维护 `Map<messageId, AbortController>`。
|
||||
|
||||
- [ ] **Step 2: preload 订阅**
|
||||
- [x] **Step 2: preload 订阅**
|
||||
|
||||
```typescript
|
||||
onAiStreamChunk: (cb) => {
|
||||
@@ -550,7 +550,7 @@ onAiStreamChunk: (cb) => {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: NetworkMonitor**
|
||||
- [x] **Step 3: NetworkMonitor**
|
||||
|
||||
`net.isOnline()` + 每 30s 检测;变化时 `webContents.send(IPC.AI_NETWORK_STATUS, { online })`。
|
||||
|
||||
@@ -570,7 +570,7 @@ git commit -am "feat: add AI streaming IPC and network monitor"
|
||||
- Modify: `src/renderer/components/layout/RightPanel.tsx`
|
||||
- Modify: `src/renderer/styles/layout.css`
|
||||
|
||||
- [ ] **Step 1: useAiStore**
|
||||
- [x] **Step 1: useAiStore**
|
||||
|
||||
```typescript
|
||||
interface AiStore {
|
||||
@@ -589,21 +589,21 @@ interface AiStore {
|
||||
|
||||
挂载时订阅 `onAiStreamChunk` / `onAiNetworkStatus`。
|
||||
|
||||
- [ ] **Step 2: AiPanel 结构**
|
||||
- [x] **Step 2: AiPanel 结构**
|
||||
|
||||
对齐 `ui_pc.html`:`ai-session-bar`、`ai-mode-tabs`(非 chat 调 `showToast(comingSoon)`)、`context-preview`(Task 9)、`offline-banner`、`chat-messages`、`quick-cmds`、`chat-input-area`。
|
||||
|
||||
`data-testid`:`ai-panel`, `ai-session-select`, `ai-chat-input`, `ai-send`, `chat-message-assistant`.
|
||||
|
||||
- [ ] **Step 3: RightPanel 替换 AI 占位**
|
||||
- [x] **Step 3: RightPanel 替换 AI 占位**
|
||||
|
||||
`panel-content` AI Tab 渲染 `<AiPanel />`。
|
||||
|
||||
- [ ] **Step 4: E2E E2E-P3-CHAT-01**
|
||||
- [x] **Step 4: E2E E2E-P3-CHAT-01**
|
||||
|
||||
`e2e/ai-chat.spec.ts`:打开书 → 新建会话 → 输入「你好」→ Enter → `chat-message-assistant` 非空(timeout 90s)。
|
||||
|
||||
- [ ] **Step 5: 验证 + Commit**
|
||||
- [x] **Step 5: 验证 + Commit**
|
||||
|
||||
```bash
|
||||
npm run test:e2e -- e2e/ai-chat.spec.ts
|
||||
@@ -621,19 +621,19 @@ git commit -am "feat: add AiPanel with streaming chat UI"
|
||||
- Modify: `src/main/ipc/handlers/ai.handler.ts`
|
||||
- Modify: `src/renderer/components/ai/AiPanel.tsx`
|
||||
|
||||
- [ ] **Step 1: AiContextBuilder 测试**
|
||||
- [x] **Step 1: AiContextBuilder 测试**
|
||||
|
||||
截断:超长 chapter content → 块 ≤ 2000 字;总量 ≤ 16KB。
|
||||
|
||||
- [ ] **Step 2: 实现 build(binding, bookId)**
|
||||
- [x] **Step 2: 实现 build(binding, bookId)**
|
||||
|
||||
返回 `{ systemPrompt, preview: ContextPreviewItem[] }`。
|
||||
|
||||
- [ ] **Step 3: ContextEditorModal**
|
||||
- [x] **Step 3: ContextEditorModal**
|
||||
|
||||
四 Tab 勾选列表(chapters/outlines/settings/inspirations);保存 → `ai:sessionUpdate` patch `context_json`;「更新上下文」→ `ai:buildContext`。
|
||||
|
||||
- [ ] **Step 4: 快捷命令**
|
||||
- [x] **Step 4: 快捷命令**
|
||||
|
||||
`src/renderer/lib/ai-slash-commands.ts`:
|
||||
|
||||
@@ -650,7 +650,7 @@ export function resolveSlashCommand(cmd: string, t: TFunction): string | null {
|
||||
|
||||
发送前:若匹配 slash,用模板替换实际 prompt。
|
||||
|
||||
- [ ] **Step 5: E2E E2E-P3-CTX-01 / E2E-P3-CMD-01**
|
||||
- [x] **Step 5: E2E E2E-P3-CTX-01 / E2E-P3-CMD-01**
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
@@ -668,7 +668,7 @@ git commit -am "feat: add AI context editor and slash commands"
|
||||
- Modify: `src/main/ipc/handlers/ai.handler.ts`(`ai:testConnection`)
|
||||
- Modify: `public/locales/*/translation.json`
|
||||
|
||||
- [ ] **Step 1: AiSettingsPage**
|
||||
- [x] **Step 1: AiSettingsPage**
|
||||
|
||||
字段:backend select、baseUrl、model、apiKey(非 lmstudio 显示)、timeout。
|
||||
|
||||
@@ -676,7 +676,7 @@ git commit -am "feat: add AI context editor and slash commands"
|
||||
|
||||
「测试连接」→ `ai:testConnection` → 最小 chat → toast。
|
||||
|
||||
- [ ] **Step 2: Settings 导航增加 AI Tab**
|
||||
- [x] **Step 2: Settings 导航增加 AI Tab**
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
@@ -696,23 +696,23 @@ git commit -am "feat: add AI settings page with connection test"
|
||||
- Modify: `src/renderer/lib/editor-commands.ts`
|
||||
- Modify: `src/renderer/components/ai/AiPanel.tsx`
|
||||
|
||||
- [ ] **Step 1: 离线规则**
|
||||
- [x] **Step 1: 离线规则**
|
||||
|
||||
`useAiStore`:`disabled = aiConfig.backend !== 'lmstudio' && !networkOnline`。
|
||||
|
||||
`AiPanel` 显示 `#offlineBanner`;网络恢复 toast。
|
||||
|
||||
- [ ] **Step 2: naming-check.service**
|
||||
- [x] **Step 2: naming-check.service**
|
||||
|
||||
`buildPrompt(settings, chaptersPlain)` → 调 `AiClientService.chat` → `parseNamingJson(response)`。
|
||||
|
||||
解析器单测(不 mock HTTP):`tests/main/naming-parse.test.ts`。
|
||||
|
||||
- [ ] **Step 3: NamingCheckModal**
|
||||
- [x] **Step 3: NamingCheckModal**
|
||||
|
||||
列表 + 忽略/替换;替换调 `search.replace`。
|
||||
|
||||
- [ ] **Step 4: AI 快照**
|
||||
- [x] **Step 4: AI 快照**
|
||||
|
||||
`insertToEditor(text)`:
|
||||
|
||||
@@ -721,7 +721,7 @@ await ipcCall(() => window.electronAPI.snapshot.create(bookId, chapterId, 'ai',
|
||||
// then insert text at cursor
|
||||
```
|
||||
|
||||
- [ ] **Step 5: E2E E2E-P3-NAME-01**
|
||||
- [x] **Step 5: E2E E2E-P3-NAME-01**
|
||||
|
||||
种子:设定「林远」+ 正文「林悦」→ 命名检查 → 至少一行结果。
|
||||
|
||||
@@ -742,15 +742,15 @@ git commit -am "feat: add offline fallback, naming check, and AI snapshots"
|
||||
- Create: `e2e/ai-chat.spec.ts`(补全 CTX/CMD/OFF)
|
||||
- Extend: `e2e/p21-features.spec.ts`
|
||||
|
||||
- [ ] **Step 1: i18n 全量键**
|
||||
- [x] **Step 1: i18n 全量键**
|
||||
|
||||
`ai.*`, `naming.*`, `inspiration.voice*`, `search.replace*`, `snapshot.ai`
|
||||
|
||||
- [ ] **Step 2: layout.css**
|
||||
- [x] **Step 2: layout.css**
|
||||
|
||||
`.ai-session-bar`, `.ai-mode-tabs`, `.chat-msg.user/.ai`, `.quick-cmd`, `.offline-banner`, `.naming-modal`, `.search-replace-panel`
|
||||
|
||||
- [ ] **Step 3: 全量测试**
|
||||
- [x] **Step 3: 全量测试**
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
@@ -760,7 +760,7 @@ npm run test:e2e
|
||||
|
||||
Expected: 全部 PASS(**LM Studio 必须在 127.0.0.1:1234 运行**)
|
||||
|
||||
- [ ] **Step 4: package.json version 0.3.0 + README**
|
||||
- [x] **Step 4: package.json version 0.3.0 + README**
|
||||
|
||||
- [ ] **Step 5: Commit + tag**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user