feat: ship v0.8.0 with writing logs and AI knowledge extraction
Deliver P5.2 writing day logs, cockpit heatmap, streak tracking, and chapter knowledge auto-extraction with merge review. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import { AiClientService } from '../../services/ai-client.service'
|
||||
import { buildFlowContextPayload, readFlowSystemPrompt } from '../../services/flow-context.util'
|
||||
import { checkInteractiveGate } from '../../services/interactive-gate.service'
|
||||
import { AutoWritingService } from '../../services/auto-writing.service'
|
||||
import { scheduleChapterExtraction } from '../../services/knowledge-extraction-runner'
|
||||
import { trackerFor } from '../../services/chapter-writing-tracker.service'
|
||||
import type { WritingLogService } from '../../services/writing-log.service'
|
||||
import { InteractiveRepository } from '../../db/repositories/interactive.repo'
|
||||
import { wrap } from '../result'
|
||||
|
||||
@@ -33,9 +36,9 @@ function readContextText(registry: BookRegistryService, bookId: string, flowId:
|
||||
export function registerAutoHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService,
|
||||
aiClient: AiClientService
|
||||
aiClient: AiClientService,
|
||||
writingLogs: WritingLogService
|
||||
): void {
|
||||
void settings
|
||||
|
||||
ipcMain.handle(IPC.AUTO_CHECK_GATE, (_e, { bookId }: { bookId: string }) =>
|
||||
wrap(() => checkInteractiveGate(registry.getDb(bookId)))
|
||||
@@ -203,7 +206,14 @@ export function registerAutoHandlers(
|
||||
(
|
||||
_e,
|
||||
{ bookId, flowId, volumeId, title }: { bookId: string; flowId: string; volumeId: string; title?: string }
|
||||
) => wrap(() => serviceFor(registry, bookId, aiClient).finishChapter(flowId, volumeId, title))
|
||||
) =>
|
||||
wrap(() => {
|
||||
const result = serviceFor(registry, bookId, aiClient).finishChapter(flowId, volumeId, title)
|
||||
const ch = registry.getChapterRepo(bookId).get(result.chapterId)!
|
||||
trackerFor(registry, bookId, writingLogs).supplementOnFinish(result.chapterId, ch.wordCount)
|
||||
scheduleChapterExtraction(registry, bookId, result.chapterId, aiClient, settings)
|
||||
return result
|
||||
})
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.AUTO_ABORT, (_e, { flowId }: { flowId: string }) =>
|
||||
|
||||
@@ -2,10 +2,15 @@ import { ipcMain } from 'electron'
|
||||
import { IPC } from '../../../shared/ipc-channels'
|
||||
import type { ChapterStatus, PublishStatus } from '../../../shared/types'
|
||||
import { BookRegistryService } from '../../services/book-registry'
|
||||
import { trackerFor } from '../../services/chapter-writing-tracker.service'
|
||||
import type { WritingLogService } from '../../services/writing-log.service'
|
||||
import { ftsSync } from '../../services/fts-sync.service'
|
||||
import { wrap } from '../result'
|
||||
|
||||
export function registerChapterHandlers(registry: BookRegistryService): void {
|
||||
export function registerChapterHandlers(
|
||||
registry: BookRegistryService,
|
||||
writingLogs: WritingLogService
|
||||
): void {
|
||||
ipcMain.handle(
|
||||
IPC.VOLUME_CREATE,
|
||||
(_event, { bookId, name }: { bookId: string; name: string }) =>
|
||||
@@ -91,6 +96,7 @@ export function registerChapterHandlers(registry: BookRegistryService): void {
|
||||
chapter.title,
|
||||
chapter.content
|
||||
)
|
||||
trackerFor(registry, bookId, writingLogs).recordDelta(chapterId, chapter.wordCount)
|
||||
registry.updateMeta(bookId, { lastChapterId: chapterId })
|
||||
return chapter
|
||||
})
|
||||
|
||||
@@ -3,16 +3,20 @@ import { IPC } from '../../../shared/ipc-channels'
|
||||
import { BookRegistryService } from '../../services/book-registry'
|
||||
import { CockpitService } from '../../services/cockpit.service'
|
||||
import type { GlobalSettingsService } from '../../services/global-settings'
|
||||
import type { WritingLogService } from '../../services/writing-log.service'
|
||||
import { wrap } from '../result'
|
||||
|
||||
export function registerCockpitHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService
|
||||
settings: GlobalSettingsService,
|
||||
writingLogs: WritingLogService
|
||||
): void {
|
||||
ipcMain.handle(
|
||||
IPC.COCKPIT_SUMMARY,
|
||||
(_e, { bookId, volumeId }: { bookId: string; volumeId?: string }) =>
|
||||
wrap(() => new CockpitService(registry.getDb(bookId), settings).getSummary(volumeId))
|
||||
wrap(() =>
|
||||
new CockpitService(registry.getDb(bookId), settings, writingLogs).getSummary(volumeId, bookId)
|
||||
)
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.COCKPIT_MARK_SEEN, (_e, { bookId }: { bookId: string }) =>
|
||||
|
||||
@@ -7,6 +7,9 @@ import { AiClientService } from '../../services/ai-client.service'
|
||||
import { buildFlowContextPayload, readFlowSystemPrompt } from '../../services/flow-context.util'
|
||||
import { checkInteractiveGate } from '../../services/interactive-gate.service'
|
||||
import { InteractiveWritingService } from '../../services/interactive-writing.service'
|
||||
import { scheduleChapterExtraction } from '../../services/knowledge-extraction-runner'
|
||||
import { trackerFor } from '../../services/chapter-writing-tracker.service'
|
||||
import type { WritingLogService } from '../../services/writing-log.service'
|
||||
import { wrap } from '../result'
|
||||
|
||||
const activeInteractive = new Map<string, AbortController>()
|
||||
@@ -24,7 +27,8 @@ function enrich(registry: BookRegistryService, bookId: string, aiClient: AiClien
|
||||
export function registerInteractiveHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService,
|
||||
aiClient: AiClientService
|
||||
aiClient: AiClientService,
|
||||
writingLogs: WritingLogService
|
||||
): void {
|
||||
ipcMain.handle(IPC.INTERACTIVE_CHECK_GATE, (_e, { bookId }: { bookId: string }) =>
|
||||
wrap(() => checkInteractiveGate(registry.getDb(bookId)))
|
||||
@@ -194,7 +198,13 @@ export function registerInteractiveHandlers(
|
||||
title
|
||||
}: { bookId: string; flowId: string; volumeId: string; title?: string }
|
||||
) =>
|
||||
wrap(() => serviceFor(registry, bookId, aiClient).finishChapter(flowId, volumeId, title))
|
||||
wrap(() => {
|
||||
const result = serviceFor(registry, bookId, aiClient).finishChapter(flowId, volumeId, title)
|
||||
const ch = registry.getChapterRepo(bookId).get(result.chapterId)!
|
||||
trackerFor(registry, bookId, writingLogs).supplementOnFinish(result.chapterId, ch.wordCount)
|
||||
scheduleChapterExtraction(registry, bookId, result.chapterId, aiClient, settings)
|
||||
return result
|
||||
})
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.INTERACTIVE_ABORT, (_e, { flowId }: { flowId: string }) =>
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { IPC } from '../../../shared/ipc-channels'
|
||||
import type { CreateKnowledgeInput, KnowledgeStatus, KnowledgeType, KnowledgeSuggestOptions } from '../../../shared/types'
|
||||
import type {
|
||||
CreateKnowledgeInput,
|
||||
KnowledgeStatus,
|
||||
KnowledgeType,
|
||||
KnowledgeSuggestOptions
|
||||
} from '../../../shared/types'
|
||||
import { BookRegistryService } from '../../services/book-registry'
|
||||
import { GlobalSettingsService } from '../../services/global-settings'
|
||||
import type { AiClientService } from '../../services/ai-client.service'
|
||||
import type { GlobalSettingsService } from '../../services/global-settings'
|
||||
import { KnowledgeRepository } from '../../db/repositories/knowledge.repo'
|
||||
import { KnowledgeExtractionService } from '../../services/knowledge-extraction.service'
|
||||
import { KnowledgeRetrievalService } from '../../services/knowledge-retrieval.service'
|
||||
import { KnowledgeService } from '../../services/knowledge.service'
|
||||
import { wrap } from '../result'
|
||||
|
||||
export function registerKnowledgeHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService
|
||||
settings: GlobalSettingsService,
|
||||
aiClient: AiClientService
|
||||
): void {
|
||||
ipcMain.handle(
|
||||
IPC.KNOWLEDGE_LIST,
|
||||
@@ -82,4 +91,33 @@ export function registerKnowledgeHandlers(
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.KNOWLEDGE_EXTRACT_CHAPTER,
|
||||
(_e, { bookId, chapterId }: { bookId: string; chapterId: string }) =>
|
||||
wrap(() =>
|
||||
new KnowledgeExtractionService(registry.getDb(bookId), aiClient).extractForChapter(chapterId)
|
||||
)
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.KNOWLEDGE_APPROVE_MERGE,
|
||||
(_e, { bookId, pendingId }: { bookId: string; pendingId: string }) =>
|
||||
wrap(() => new KnowledgeService(registry.getDb(bookId)).approveMerge(pendingId))
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.KNOWLEDGE_SAVE_MERGE_AS_NEW,
|
||||
(_e, { bookId, pendingId }: { bookId: string; pendingId: string }) =>
|
||||
wrap(() => new KnowledgeService(registry.getDb(bookId)).saveMergeAsNew(pendingId))
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.KNOWLEDGE_BATCH_APPROVE_HIGH_CONFIDENCE,
|
||||
(_e, { bookId, threshold }: { bookId: string; threshold?: number }) =>
|
||||
wrap(() => {
|
||||
const t = threshold ?? settings.get().knowledgeExtractConfidenceThreshold ?? 0.8
|
||||
return new KnowledgeService(registry.getDb(bookId)).batchApproveHighConfidence(t)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { IPC } from '../../../shared/ipc-channels'
|
||||
import type { WritingLogService } from '../../services/writing-log.service'
|
||||
import { wrap } from '../result'
|
||||
|
||||
export function registerWritingHandlers(writingLogs: WritingLogService): void {
|
||||
ipcMain.handle(IPC.WRITING_GET_STATS, (_e, { bookId }: { bookId: string }) =>
|
||||
wrap(() => writingLogs.getStats(bookId))
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user