Files
bilin/src/preload/index.ts
T
bing a39e06ff07 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>
2026-07-07 18:26:29 +08:00

552 lines
24 KiB
TypeScript

import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'
import { IPC } from '../shared/ipc-channels'
import type {
KnowledgeSuggestOptions,
ScoredKnowledge,
ActionId,
Bookmark,
BookMeta,
BookOpenResult,
Chapter,
CreateBookParams,
GlobalSettings,
InspirationEntry,
AiContextBinding,
AiContextBuildResult,
AiConfig,
AiMessage,
AiSession,
NamingConflict,
AiStreamChunkEvent,
AiNetworkStatusEvent,
AutoRhythm,
AutoWritingConfig,
InteractiveFlow,
InteractiveGateResult,
InteractiveStreamChunkEvent,
PlotOption,
PlotSelection,
IpcResult,
LandmarkType,
OutlineItem,
OutlineStatus,
SearchResult,
SettingEntry,
SettingType,
Snapshot,
SnapshotType,
UpdateChapterParams,
Volume,
WordFreqResult,
KnowledgeEntry,
CreateKnowledgeInput,
KnowledgeStatus,
KnowledgeType,
CockpitSummary,
ChapterBridgeResult,
PublishStatus,
WritingStats,
KnowledgeExtractionResult
} from '../shared/types'
const electronAPI = {
window: {
minimize: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_MINIMIZE),
maximize: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_MAXIMIZE),
close: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_CLOSE)
},
settings: {
get: (): Promise<IpcResult<GlobalSettings>> => ipcRenderer.invoke(IPC.SETTINGS_GET),
update: (partial: Partial<GlobalSettings>): Promise<IpcResult<GlobalSettings>> =>
ipcRenderer.invoke(IPC.SETTINGS_UPDATE, partial)
},
book: {
list: (): Promise<IpcResult<BookMeta[]>> => ipcRenderer.invoke(IPC.BOOK_LIST),
create: (params: CreateBookParams): Promise<IpcResult<BookMeta>> =>
ipcRenderer.invoke(IPC.BOOK_CREATE, params),
delete: (bookId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.BOOK_DELETE, { bookId }),
open: (bookId: string): Promise<IpcResult<BookOpenResult>> =>
ipcRenderer.invoke(IPC.BOOK_OPEN, { bookId }),
updateMeta: (
bookId: string,
patch: { lastChapterId?: string | null; status?: BookMeta['status'] }
): Promise<IpcResult<BookMeta>> => ipcRenderer.invoke(IPC.BOOK_UPDATE_META, { bookId, ...patch })
},
volume: {
create: (bookId: string, name: string): Promise<IpcResult<Volume>> =>
ipcRenderer.invoke(IPC.VOLUME_CREATE, { bookId, name }),
update: (
bookId: string,
volumeId: string,
patch: { name?: string; description?: string }
): Promise<IpcResult<Volume>> =>
ipcRenderer.invoke(IPC.VOLUME_UPDATE, { bookId, volumeId, ...patch }),
delete: (bookId: string, volumeId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.VOLUME_DELETE, { bookId, volumeId })
},
chapter: {
create: (bookId: string, volumeId: string, title: string): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_CREATE, { bookId, volumeId, title }),
get: (bookId: string, chapterId: string): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_GET, { bookId, chapterId }),
update: (params: UpdateChapterParams): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_UPDATE, params),
delete: (bookId: string, chapterId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.CHAPTER_DELETE, { bookId, chapterId }),
reorder: (bookId: string, volumeId: string, orderedIds: string[]): Promise<IpcResult<Chapter[]>> =>
ipcRenderer.invoke(IPC.CHAPTER_REORDER, { bookId, volumeId, orderedIds }),
moveToVolume: (
bookId: string,
chapterId: string,
targetVolumeId: string,
targetIndex: number
): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_MOVE_TO_VOLUME, { bookId, chapterId, targetVolumeId, targetIndex }),
setPublishStatus: (
bookId: string,
chapterId: string,
publishStatus: PublishStatus
): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_SET_PUBLISH_STATUS, { bookId, chapterId, publishStatus })
},
outline: {
list: (bookId: string): Promise<IpcResult<OutlineItem[]>> =>
ipcRenderer.invoke(IPC.OUTLINE_LIST, { bookId }),
create: (
bookId: string,
title: string,
parentId?: string | null,
sortOrder?: number
): Promise<IpcResult<OutlineItem>> =>
ipcRenderer.invoke(IPC.OUTLINE_CREATE, { bookId, parentId, title, sortOrder }),
update: (
bookId: string,
id: string,
patch: Partial<{
title: string
description: string
status: OutlineStatus
expectedWordCount: number | null
chapterId: string | null
sortOrder: number
}>
): Promise<IpcResult<OutlineItem>> =>
ipcRenderer.invoke(IPC.OUTLINE_UPDATE, { bookId, id, patch }),
delete: (bookId: string, id: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.OUTLINE_DELETE, { bookId, id }),
move: (
bookId: string,
id: string,
parentId: string | null,
sortOrder: number
): Promise<IpcResult<OutlineItem>> =>
ipcRenderer.invoke(IPC.OUTLINE_MOVE, { bookId, id, parentId, sortOrder })
},
setting: {
list: (bookId: string, type?: SettingType): Promise<IpcResult<SettingEntry[]>> =>
ipcRenderer.invoke(IPC.SETTING_LIST, { bookId, type }),
create: (bookId: string, type: SettingType, name: string): Promise<IpcResult<SettingEntry>> =>
ipcRenderer.invoke(IPC.SETTING_CREATE, { bookId, type, name }),
update: (
bookId: string,
id: string,
patch: Partial<{ name: string; description: string; type: SettingType; properties: Record<string, unknown> }>
): Promise<IpcResult<SettingEntry>> =>
ipcRenderer.invoke(IPC.SETTING_UPDATE, { bookId, id, patch }),
delete: (bookId: string, id: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SETTING_DELETE, { bookId, id }),
syncRefs: (bookId: string, id: string, chapterIds: string[]): Promise<IpcResult<SettingEntry>> =>
ipcRenderer.invoke(IPC.SETTING_SYNC_REFS, { bookId, id, chapterIds })
},
inspiration: {
list: (bookId: string): Promise<IpcResult<InspirationEntry[]>> =>
ipcRenderer.invoke(IPC.INSPIRATION_LIST, { bookId }),
create: (
bookId: string,
title?: string,
content?: string,
tags?: string[]
): Promise<IpcResult<InspirationEntry>> =>
ipcRenderer.invoke(IPC.INSPIRATION_CREATE, { bookId, title, content, tags }),
update: (
bookId: string,
id: string,
patch: Partial<{ title: string; content: string; tags: string[] }>
): Promise<IpcResult<InspirationEntry>> =>
ipcRenderer.invoke(IPC.INSPIRATION_UPDATE, { bookId, id, patch }),
delete: (bookId: string, id: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.INSPIRATION_DELETE, { bookId, id }),
convert: (
bookId: string,
id: string,
targetKind: 'outline' | 'setting',
settingType?: SettingType
): Promise<IpcResult<OutlineItem | SettingEntry>> =>
ipcRenderer.invoke(IPC.INSPIRATION_CONVERT, { bookId, id, targetKind, settingType })
},
snapshot: {
list: (bookId: string, chapterId: string): Promise<IpcResult<Snapshot[]>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_LIST, { bookId, chapterId }),
create: (
bookId: string,
chapterId: string,
type: SnapshotType,
name?: string,
content?: string
): Promise<IpcResult<Snapshot>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_CREATE, { bookId, chapterId, type, name, content }),
restore: (
bookId: string,
chapterId: string,
snapshotId: string
): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_RESTORE, { bookId, chapterId, snapshotId }),
delete: (bookId: string, snapshotId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_DELETE, { bookId, snapshotId }),
startAutoSave: (bookId: string, chapterId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_START_AUTO, { bookId, chapterId }),
stopAutoSave: (bookId: string, chapterId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SNAPSHOT_STOP_AUTO, { bookId, chapterId })
},
bookmark: {
list: (
bookId: string,
chapterId?: string,
resolved?: boolean
): Promise<IpcResult<Bookmark[]>> =>
ipcRenderer.invoke(IPC.BOOKMARK_LIST, { bookId, chapterId, resolved }),
create: (
bookId: string,
chapterId: string,
position: number,
label: string,
landmarkType?: LandmarkType
): Promise<IpcResult<Bookmark>> =>
ipcRenderer.invoke(IPC.BOOKMARK_CREATE, { bookId, chapterId, position, label, landmarkType }),
update: (
bookId: string,
id: string,
patch: Partial<{ label: string; position: number; landmarkType: LandmarkType }>
): Promise<IpcResult<Bookmark>> =>
ipcRenderer.invoke(IPC.BOOKMARK_UPDATE, { bookId, id, patch }),
resolve: (bookId: string, id: string, resolved: boolean): Promise<IpcResult<Bookmark>> =>
ipcRenderer.invoke(IPC.BOOKMARK_RESOLVE, { bookId, id, resolved }),
delete: (bookId: string, id: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.BOOKMARK_DELETE, { bookId, id })
},
search: {
query: (
bookId: string,
query: string,
options?: { regex?: boolean; caseSensitive?: boolean; matchWholeWord?: boolean }
): Promise<IpcResult<SearchResult[]>> =>
ipcRenderer.invoke(IPC.SEARCH_QUERY, { bookId, query, options }),
replace: (
bookId: string,
query: string,
replacement: string,
dryRun: boolean,
options?: { regex?: boolean; caseSensitive?: boolean }
): Promise<IpcResult<{ count: number; previews: SearchResult[] }>> =>
ipcRenderer.invoke(IPC.SEARCH_REPLACE, { bookId, query, replacement, dryRun, options }),
getHistory: (): Promise<IpcResult<string[]>> => ipcRenderer.invoke(IPC.SEARCH_GET_HISTORY),
saveHistory: (query: string): Promise<IpcResult<string[]>> =>
ipcRenderer.invoke(IPC.SEARCH_SAVE_HISTORY, { query })
},
ai: {
sessionList: (bookId: string, includeArchived?: boolean): Promise<IpcResult<AiSession[]>> =>
ipcRenderer.invoke(IPC.AI_SESSION_LIST, { bookId, includeArchived }),
sessionCreate: (bookId: string, title?: string, model?: string): Promise<IpcResult<AiSession>> =>
ipcRenderer.invoke(IPC.AI_SESSION_CREATE, { bookId, title, model }),
sessionUpdate: (
bookId: string,
sessionId: string,
patch: Partial<{ title: string; model: string; archived: boolean; context: AiContextBinding }>
): Promise<IpcResult<AiSession>> =>
ipcRenderer.invoke(IPC.AI_SESSION_UPDATE, { bookId, sessionId, patch }),
sessionDelete: (bookId: string, sessionId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.AI_SESSION_DELETE, { bookId, sessionId }),
messageList: (bookId: string, sessionId: string): Promise<IpcResult<AiMessage[]>> =>
ipcRenderer.invoke(IPC.AI_MESSAGE_LIST, { bookId, sessionId }),
chat: (
bookId: string,
sessionId: string,
content: string,
prompt?: string
): Promise<IpcResult<{ messageId: string }>> =>
ipcRenderer.invoke(IPC.AI_CHAT, { bookId, sessionId, content, prompt }),
abort: (messageId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.AI_ABORT, { messageId }),
buildContext: (
bookId: string,
binding: AiContextBinding
): Promise<IpcResult<AiContextBuildResult>> =>
ipcRenderer.invoke(IPC.AI_BUILD_CONTEXT, { bookId, binding }),
testConnection: (config?: AiConfig): Promise<IpcResult<{ ok: true }>> =>
ipcRenderer.invoke(IPC.AI_TEST_CONNECTION, { config }),
namingCheck: (bookId: string): Promise<IpcResult<NamingConflict[]>> =>
ipcRenderer.invoke(IPC.AI_NAMING_CHECK, { bookId })
},
interactive: {
checkGate: (bookId: string): Promise<IpcResult<InteractiveGateResult>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_CHECK_GATE, { bookId }),
getFlow: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow | null>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_GET_FLOW, { bookId, sessionId }),
start: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_START, { bookId, sessionId }),
confirmContext: (
bookId: string,
flowId: string,
binding: AiContextBinding
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_CONFIRM_CONTEXT, { bookId, flowId, binding }),
suggestPlots: (bookId: string, flowId: string): Promise<IpcResult<PlotOption[]>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_SUGGEST_PLOTS, { bookId, flowId }),
selectPlot: (
bookId: string,
flowId: string,
selection: PlotSelection
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_SELECT_PLOT, { bookId, flowId, selection }),
generateScene: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_GENERATE_SCENE, { bookId, flowId }),
resolveNaming: (
bookId: string,
flowId: string,
chosenName: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_RESOLVE_NAMING, { bookId, flowId, chosenName }),
refineScene: (
bookId: string,
flowId: string,
instruction: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_REFINE_SCENE, { bookId, flowId, instruction }),
acceptScene: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_ACCEPT_SCENE, { bookId, flowId }),
finishChapter: (
bookId: string,
flowId: string,
volumeId: string,
title?: string
): Promise<IpcResult<{ chapterId: string }>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_FINISH_CHAPTER, { bookId, flowId, volumeId, title }),
abort: (flowId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_ABORT, { flowId }),
getSceneDraft: (bookId: string, flowId: string): Promise<IpcResult<string>> =>
ipcRenderer.invoke(IPC.INTERACTIVE_GET_SCENE_DRAFT, { bookId, flowId })
},
auto: {
checkGate: (bookId: string): Promise<IpcResult<InteractiveGateResult>> =>
ipcRenderer.invoke(IPC.AUTO_CHECK_GATE, { bookId }),
getFlow: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow | null>> =>
ipcRenderer.invoke(IPC.AUTO_GET_FLOW, { bookId, sessionId }),
start: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_START, { bookId, sessionId }),
setGoal: (
bookId: string,
flowId: string,
config: Partial<AutoWritingConfig>
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_SET_GOAL, { bookId, flowId, config }),
confirmContext: (
bookId: string,
flowId: string,
binding: AiContextBinding
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_CONFIRM_CONTEXT, { bookId, flowId, binding }),
planScenes: (
bookId: string,
flowId: string,
contextSummary?: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_PLAN_SCENES, { bookId, flowId, contextSummary }),
generate: (
bookId: string,
flowId: string,
contextSummary?: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_GENERATE, { bookId, flowId, contextSummary }),
resolveNaming: (
bookId: string,
flowId: string,
chosenName: string,
contextSummary?: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_RESOLVE_NAMING, { bookId, flowId, chosenName, contextSummary }),
pause: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_PAUSE, { bookId, flowId }),
resume: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_RESUME, { bookId, flowId }),
handoffInteractive: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.AUTO_HANDOFF_INTERACTIVE, { bookId, flowId }),
finishChapter: (
bookId: string,
flowId: string,
volumeId: string,
title?: string
): Promise<IpcResult<{ chapterId: string }>> =>
ipcRenderer.invoke(IPC.AUTO_FINISH_CHAPTER, { bookId, flowId, volumeId, title }),
abort: (flowId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.AUTO_ABORT, { flowId }),
getSceneDraft: (bookId: string, flowId: string): Promise<IpcResult<string>> =>
ipcRenderer.invoke(IPC.AUTO_GET_SCENE_DRAFT, { bookId, flowId })
},
wizard: {
getFlow: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow | null>> =>
ipcRenderer.invoke(IPC.WIZARD_GET_FLOW, { bookId, sessionId }),
start: (bookId: string, sessionId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_START, { bookId, sessionId }),
setGoal: (
bookId: string,
flowId: string,
config: Partial<AutoWritingConfig>
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_SET_GOAL, { bookId, flowId, config }),
setRhythm: (
bookId: string,
flowId: string,
rhythm: AutoRhythm,
styleNote?: string
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_SET_RHYTHM, { bookId, flowId, rhythm, styleNote }),
setPov: (bookId: string, flowId: string, povSettingId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_SET_POV, { bookId, flowId, povSettingId }),
confirmContext: (
bookId: string,
flowId: string,
binding: AiContextBinding
): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_CONFIRM_CONTEXT, { bookId, flowId, binding }),
buildPreview: (
bookId: string,
flowId: string
): Promise<IpcResult<{ preview: string; flow: InteractiveFlow }>> =>
ipcRenderer.invoke(IPC.WIZARD_BUILD_PREVIEW, { bookId, flowId }),
startGenerate: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_START_GENERATE, { bookId, flowId }),
handoffInteractive: (bookId: string, flowId: string): Promise<IpcResult<InteractiveFlow>> =>
ipcRenderer.invoke(IPC.WIZARD_HANDOFF_INTERACTIVE, { bookId, flowId }),
abort: (flowId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.WIZARD_ABORT, { flowId })
},
knowledge: {
list: (
bookId: string,
filter?: { status?: KnowledgeStatus; type?: KnowledgeType; forgottenOnly?: boolean }
): Promise<IpcResult<KnowledgeEntry[]>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_LIST, { bookId, filter }),
create: (bookId: string, input: CreateKnowledgeInput): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_CREATE, { bookId, input }),
update: (
bookId: string,
id: string,
patch: Partial<CreateKnowledgeInput & { status: KnowledgeStatus }>
): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_UPDATE, { bookId, id, patch }),
delete: (bookId: string, id: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_DELETE, { bookId, id }),
approve: (bookId: string, id: string): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_APPROVE, { bookId, id }),
reject: (bookId: string, id: string): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_REJECT, { bookId, id }),
batchApprove: (bookId: string, ids: string[]): Promise<IpcResult<number>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_BATCH_APPROVE, { bookId, ids }),
createFromBookmark: (bookId: string, bookmarkId: string): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_CREATE_FROM_BOOKMARK, { bookId, bookmarkId }),
stats: (
bookId: string
): Promise<IpcResult<{ buried: number; resolved: number; forgotten: number }>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_STATS, { bookId }),
suggestContext: (
bookId: string,
opts?: KnowledgeSuggestOptions
): Promise<IpcResult<ScoredKnowledge[]>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_SUGGEST_CONTEXT, { bookId, opts }),
extractChapter: (
bookId: string,
chapterId: string
): Promise<IpcResult<KnowledgeExtractionResult>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_EXTRACT_CHAPTER, { bookId, chapterId }),
approveMerge: (bookId: string, pendingId: string): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_APPROVE_MERGE, { bookId, pendingId }),
saveMergeAsNew: (bookId: string, pendingId: string): Promise<IpcResult<KnowledgeEntry>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_SAVE_MERGE_AS_NEW, { bookId, pendingId }),
batchApproveHighConfidence: (
bookId: string,
threshold?: number
): Promise<IpcResult<number>> =>
ipcRenderer.invoke(IPC.KNOWLEDGE_BATCH_APPROVE_HIGH_CONFIDENCE, { bookId, threshold })
},
writing: {
getStats: (bookId: string): Promise<IpcResult<WritingStats>> =>
ipcRenderer.invoke(IPC.WRITING_GET_STATS, { bookId })
},
cockpit: {
getSummary: (bookId: string, volumeId?: string): Promise<IpcResult<CockpitSummary>> =>
ipcRenderer.invoke(IPC.COCKPIT_SUMMARY, { bookId, volumeId }),
markSeen: (bookId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.COCKPIT_MARK_SEEN, { bookId }),
shouldShowOnOpen: (bookId: string): Promise<IpcResult<boolean>> =>
ipcRenderer.invoke(IPC.COCKPIT_SHOULD_SHOW, { bookId })
},
bridge: {
get: (
bookId: string,
chapterId: string,
withAi?: boolean
): Promise<IpcResult<ChapterBridgeResult>> =>
ipcRenderer.invoke(IPC.BRIDGE_GET, { bookId, chapterId, withAi }),
dismiss: (bookId: string, chapterId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.BRIDGE_DISMISS, { bookId, chapterId }),
shouldPrompt: (bookId: string, chapterId: string): Promise<IpcResult<boolean>> =>
ipcRenderer.invoke(IPC.BRIDGE_SHOULD_PROMPT, { bookId, chapterId })
},
wordfreq: {
analyze: (
bookId: string,
scope: { kind: 'book' | 'volume' | 'chapter'; chapterId?: string; volumeId?: string }
): Promise<IpcResult<WordFreqResult>> =>
ipcRenderer.invoke(IPC.WORDFREQ_ANALYZE, { bookId, scope })
},
shortcut: {
getAll: (): Promise<IpcResult<Record<ActionId, string>>> =>
ipcRenderer.invoke(IPC.SHORTCUT_GET_ALL),
register: (action: ActionId, accelerator: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SHORTCUT_REGISTER, { action, accelerator })
},
onShortcutTriggered: (callback: (action: ActionId) => void): (() => void) => {
const handler = (_event: IpcRendererEvent, payload: { action: ActionId }) => {
callback(payload.action)
}
ipcRenderer.on(IPC.SHORTCUT_TRIGGERED, handler)
return () => ipcRenderer.removeListener(IPC.SHORTCUT_TRIGGERED, handler)
},
onAiStreamChunk: (callback: (payload: AiStreamChunkEvent) => void): (() => void) => {
const handler = (_event: IpcRendererEvent, payload: AiStreamChunkEvent) => {
callback(payload)
}
ipcRenderer.on(IPC.AI_STREAM_CHUNK, handler)
return () => ipcRenderer.removeListener(IPC.AI_STREAM_CHUNK, handler)
},
onAiNetworkStatus: (callback: (payload: AiNetworkStatusEvent) => void): (() => void) => {
const handler = (_event: IpcRendererEvent, payload: AiNetworkStatusEvent) => {
callback(payload)
}
ipcRenderer.on(IPC.AI_NETWORK_STATUS, handler)
return () => ipcRenderer.removeListener(IPC.AI_NETWORK_STATUS, handler)
},
onInteractiveStreamChunk: (callback: (payload: InteractiveStreamChunkEvent) => void): (() => void) => {
const handler = (_event: IpcRendererEvent, payload: InteractiveStreamChunkEvent) => {
callback(payload)
}
ipcRenderer.on(IPC.INTERACTIVE_STREAM_CHUNK, handler)
return () => ipcRenderer.removeListener(IPC.INTERACTIVE_STREAM_CHUNK, handler)
}
}
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
export type ElectronAPI = typeof electronAPI