Files
bilin/src/main/services/global-settings.ts
T
bing adf877861d feat: ship v1.1.0 writer toolkit with templates, import, export, and inspiration capture
Implement chapter templates, txt/md/docx import, submission export presets, and global inspiration shortcut. Split import handlers into a separate main bundle and fix EditorLayout setTarget loop that broke E2E.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 13:53:22 +08:00

92 lines
3.1 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import { DEFAULT_SHORTCUTS } from '../../shared/default-shortcuts'
import { DEFAULT_AI_CONFIG, type GlobalSettings } from '../../shared/types'
const FILE = 'global_settings.json'
function defaultAiConfig() {
return {
...DEFAULT_AI_CONFIG,
baseUrl: process.env.BILIN_AI_BASE_URL ?? DEFAULT_AI_CONFIG.baseUrl,
model: process.env.BILIN_AI_MODEL ?? DEFAULT_AI_CONFIG.model
}
}
function defaults(): GlobalSettings {
return {
penName: '未命名作者',
onboardingCompleted: false,
theme: 'default',
language: 'zh-CN',
dailyWordGoal: 0,
updateSchedule: 'none',
stockBufferThreshold: 3,
shortcuts: { ...DEFAULT_SHORTCUTS },
snapshotIntervalMs: 300_000,
snapshotMaxAuto: 20,
snapshotMaxPersist: 3,
searchHistory: [],
aiConfig: defaultAiConfig(),
namingIgnoreWords: [],
knowledgeAutoSuggest: true,
knowledgeSuggestTopN: 8,
knowledgeAutoExtract: true,
knowledgeExtractConfidenceThreshold: 0.8,
pomodoroDurationMinutes: 25,
enableSystemNotifications: false,
enableGoalNotifications: true,
chapterTemplates: [],
submissionPresets: [],
defaultSubmissionPresetId: 'builtin-webnovel'
}
}
export class GlobalSettingsService {
constructor(private userDataDir: string) {
if (!existsSync(userDataDir)) mkdirSync(userDataDir, { recursive: true })
}
private filePath(): string {
return join(this.userDataDir, FILE)
}
get(): GlobalSettings {
if (!existsSync(this.filePath())) return defaults()
const raw = JSON.parse(readFileSync(this.filePath(), 'utf-8')) as Partial<GlobalSettings>
return {
...defaults(),
...raw,
updateSchedule: raw.updateSchedule ?? 'none',
stockBufferThreshold: raw.stockBufferThreshold ?? 3,
knowledgeAutoSuggest: raw.knowledgeAutoSuggest ?? true,
knowledgeSuggestTopN: raw.knowledgeSuggestTopN ?? 8,
knowledgeAutoExtract: raw.knowledgeAutoExtract ?? true,
knowledgeExtractConfidenceThreshold: raw.knowledgeExtractConfidenceThreshold ?? 0.8,
pomodoroDurationMinutes: raw.pomodoroDurationMinutes ?? 25,
enableSystemNotifications: raw.enableSystemNotifications ?? false,
enableGoalNotifications: raw.enableGoalNotifications ?? true,
dailyGoalNotifiedDate: raw.dailyGoalNotifiedDate,
chapterTemplates: raw.chapterTemplates ?? [],
submissionPresets: raw.submissionPresets ?? [],
defaultSubmissionPresetId: raw.defaultSubmissionPresetId ?? 'builtin-webnovel',
shortcuts: { ...DEFAULT_SHORTCUTS, ...raw.shortcuts },
aiConfig: { ...DEFAULT_AI_CONFIG, ...raw.aiConfig }
}
}
update(partial: Partial<GlobalSettings>): GlobalSettings {
const current = this.get()
const next: GlobalSettings = {
...current,
...partial,
shortcuts: partial.shortcuts
? { ...current.shortcuts, ...partial.shortcuts }
: current.shortcuts,
aiConfig: partial.aiConfig ? { ...current.aiConfig, ...partial.aiConfig } : current.aiConfig
}
writeFileSync(this.filePath(), JSON.stringify(next, null, 2), 'utf-8')
return next
}
}