feat: ship v1.2.0 Wave 1 foundation with book settings, chapter management, and focus mode
Deliver schema v9, book/chapter IPC extensions, BookSettingsTab, chapter meta/tags, AI session menu, focus mode and TTS, template context enrich, and Wave 1 E2E coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { BrowserWindow, dialog, ipcMain } from 'electron'
|
||||
import { IPC } from '../../../shared/ipc-channels'
|
||||
import type { AiConfig, AiContextBinding, NamingConflict } from '../../../shared/types'
|
||||
import { BookRegistryService } from '../../services/book-registry'
|
||||
@@ -19,6 +20,15 @@ import { wrap } from '../result'
|
||||
|
||||
const activeChats = new Map<string, AbortController>()
|
||||
|
||||
function formatSessionMarkdown(title: string, messages: { role: string; content: string; createdAt: string }[]): string {
|
||||
const lines = [`# ${title || 'AI 会话'}`, '']
|
||||
for (const msg of messages) {
|
||||
const label = msg.role === 'user' ? '用户' : msg.role === 'assistant' ? '助手' : msg.role
|
||||
lines.push(`## ${label}`, '', msg.content, '', `_${msg.createdAt}_`, '')
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export function registerAiHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService,
|
||||
@@ -67,6 +77,39 @@ export function registerAiHandlers(
|
||||
wrap(() => registry.getAiSessionRepo(bookId).listMessages(sessionId))
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.AI_SESSION_EXPORT,
|
||||
async (
|
||||
_e,
|
||||
{
|
||||
bookId,
|
||||
sessionId,
|
||||
format = 'markdown'
|
||||
}: { bookId: string; sessionId: string; format?: 'markdown' | 'txt' }
|
||||
) =>
|
||||
wrap(async () => {
|
||||
const repo = registry.getAiSessionRepo(bookId)
|
||||
const session = repo.get(sessionId)
|
||||
if (!session) throw new Error('AI session not found')
|
||||
const messages = repo.listMessages(sessionId)
|
||||
const text =
|
||||
format === 'markdown'
|
||||
? formatSessionMarkdown(session.title, messages)
|
||||
: messages
|
||||
.map((m) => `[${m.role}] ${m.createdAt}\n${m.content}`)
|
||||
.join('\n\n---\n\n')
|
||||
|
||||
const ext = format === 'markdown' ? 'md' : 'txt'
|
||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||
defaultPath: `${session.title || 'ai-session'}.${ext}`,
|
||||
filters: [{ name: format === 'markdown' ? 'Markdown' : 'Text', extensions: [ext] }]
|
||||
})
|
||||
if (canceled || !filePath) return null
|
||||
writeFileSync(filePath, text, 'utf8')
|
||||
return filePath
|
||||
})
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.AI_BUILD_CONTEXT,
|
||||
(_e, { bookId, binding }: { bookId: string; binding: AiContextBinding }) =>
|
||||
|
||||
Reference in New Issue
Block a user