Files
CipherTalk/electron/services/config.ts
T
ILoveBingLu ff05dbaa32 feat(chat): 新增聊天记录独立窗口和日期查询功能
- 新增聊天记录独立窗口(ChatHistoryPage),支持在单独窗口中查看完整聊天记录
- 实现 createChatHistoryWindow 函数,支持窗口复用和主题适配
- 新增 IPC 处理器用于打开聊天记录窗口和获取单条消息
- 添加 getMessagesByDate 和 getDatesWithMessages 方法,支持按日期查询消息
- 在 preload.ts 中暴露新的 IPC 调用接口
- 新增 ChatHistoryPage.tsx 和 ChatHistoryPage.scss 组件文件
- 更新 package.json 依赖项和 package-lock.json
- 更新 README.md,新增爱发电赞助支持入口
- 添加爱发电二维码图片资源
- 版本号更新至 2.1.6
- 优化聊天页面和设置页面的用户体验
- 更新类型定义和配置文件以支持新功能
2026-01-29 15:53:56 +08:00

307 lines
8.6 KiB
TypeScript

import Database from 'better-sqlite3'
import { app } from 'electron'
import path from 'path'
import fs from 'fs'
interface ConfigSchema {
// 数据库相关
dbPath: string
decryptKey: string
myWxid: string
// 图片解密相关
imageXorKey: string
imageAesKey: string
// 缓存相关
cachePath: string
lastOpenedDb: string
lastSession: string
// 导出相关
exportPath: string
// 界面相关
theme: string
themeMode: string
language: string
// 协议相关
agreementVersion: number
// 激活相关
activationData: string
// STT 相关
sttLanguages: string[]
sttModelType: 'int8' | 'float32'
// 日志相关
logLevel: string
// 数据管理相关
skipIntegrityCheck: boolean
autoUpdateDatabase: boolean // 是否自动更新数据库
// AI 相关
aiCurrentProvider: string // 当前选中的提供商
aiProviderConfigs: { // 每个提供商的独立配置
[providerId: string]: {
apiKey: string
model: string
}
}
aiDefaultTimeRange: number
aiSummaryDetail: 'simple' | 'normal' | 'detailed'
aiEnableCache: boolean
aiEnableThinking: boolean // 是否显示思考过程
}
const defaults: ConfigSchema = {
dbPath: '',
decryptKey: '',
myWxid: '',
imageXorKey: '',
imageAesKey: '',
cachePath: '',
lastOpenedDb: '',
lastSession: '',
exportPath: '',
theme: 'cloud-dancer',
themeMode: 'light',
language: 'zh-CN',
sttLanguages: ['zh'],
sttModelType: 'int8',
agreementVersion: 0,
activationData: '',
logLevel: 'WARN', // 默认只记录警告和错误
skipIntegrityCheck: false, // 默认进行完整性检查
autoUpdateDatabase: true, // 默认开启自动更新
// AI 默认配置
aiCurrentProvider: 'zhipu',
aiProviderConfigs: {}, // 空对象,用户配置后填充
aiDefaultTimeRange: 7, // 默认7天
aiSummaryDetail: 'normal',
aiEnableCache: true,
aiEnableThinking: true // 默认显示思考过程
}
export class ConfigService {
private db: Database.Database | null = null
private dbPath: string
constructor() {
const userDataPath = app.getPath('userData')
this.dbPath = path.join(userDataPath, 'ciphertalk-config.db')
this.initDatabase()
}
private initDatabase(): void {
try {
// 确保目录存在
const dir = path.dirname(this.dbPath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
this.db = new Database(this.dbPath)
// 创建配置表
this.db.exec(`
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT
)
`)
// 创建 TLD 缓存表
this.db.exec(`
CREATE TABLE IF NOT EXISTS tld_cache (
id INTEGER PRIMARY KEY CHECK (id = 1),
tlds TEXT,
updated_at INTEGER
)
`)
// 初始化默认值
const insertStmt = this.db.prepare(`
INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)
`)
for (const [key, value] of Object.entries(defaults)) {
insertStmt.run(key, JSON.stringify(value))
}
// 迁移:修复旧版本产生的空 STT 语言配置,默认为中文
try {
const sttRow = this.db.prepare("SELECT value FROM config WHERE key = 'sttLanguages'").get() as { value: string } | undefined
if (sttRow) {
const langs = JSON.parse(sttRow.value)
if (Array.isArray(langs) && langs.length === 0) {
this.db.prepare("UPDATE config SET value = ? WHERE key = 'sttLanguages'").run(JSON.stringify(['zh']))
}
}
} catch (e) {
console.error('迁移 STT 配置失败:', e)
}
// 迁移:将旧的 AI 配置迁移到新结构(支持多提供商)
try {
const oldProviderRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiProvider'").get() as { value: string } | undefined
const oldApiKeyRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiApiKey'").get() as { value: string } | undefined
const oldModelRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiModel'").get() as { value: string } | undefined
if (oldProviderRow && oldApiKeyRow) {
const oldProvider = JSON.parse(oldProviderRow.value)
const oldApiKey = JSON.parse(oldApiKeyRow.value)
const oldModel = oldModelRow ? JSON.parse(oldModelRow.value) : ''
// 如果有旧配置且 API Key 不为空,迁移到新结构
if (oldApiKey) {
const newConfigs: any = {}
newConfigs[oldProvider] = {
apiKey: oldApiKey,
model: oldModel
}
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run('aiCurrentProvider', JSON.stringify(oldProvider))
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run('aiProviderConfigs', JSON.stringify(newConfigs))
// 删除旧配置
this.db.prepare("DELETE FROM config WHERE key IN ('aiProvider', 'aiApiKey', 'aiModel')").run()
console.log('[Config] AI 配置已迁移到新结构')
}
}
} catch (e) {
console.error('迁移 AI 配置失败:', e)
}
} catch (e) {
console.error('初始化配置数据库失败:', e)
}
}
get<K extends keyof ConfigSchema>(key: K): ConfigSchema[K] {
try {
if (!this.db) {
return defaults[key]
}
const row = this.db.prepare('SELECT value FROM config WHERE key = ?').get(key) as { value: string } | undefined
if (row) {
return JSON.parse(row.value)
}
return defaults[key]
} catch (e) {
console.error(`获取配置 ${key} 失败:`, e)
return defaults[key]
}
}
set<K extends keyof ConfigSchema>(key: K, value: ConfigSchema[K]): void {
try {
if (!this.db) return
this.db.prepare(`
INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)
`).run(key, JSON.stringify(value))
} catch (e) {
console.error(`设置配置 ${key} 失败:`, e)
}
}
getAll(): ConfigSchema {
try {
if (!this.db) {
return { ...defaults }
}
const rows = this.db.prepare('SELECT key, value FROM config').all() as { key: string; value: string }[]
const result = { ...defaults }
for (const row of rows) {
if (row.key in defaults) {
(result as any)[row.key] = JSON.parse(row.value)
}
}
return result
} catch (e) {
console.error('获取所有配置失败:', e)
return { ...defaults }
}
}
clear(): void {
try {
if (!this.db) return
this.db.exec('DELETE FROM config')
// 重新插入默认值
const insertStmt = this.db.prepare(`
INSERT INTO config (key, value) VALUES (?, ?)
`)
for (const [key, value] of Object.entries(defaults)) {
insertStmt.run(key, JSON.stringify(value))
}
} catch (e) {
console.error('清除配置失败:', e)
}
}
close(): void {
if (this.db) {
this.db.close()
this.db = null
}
}
// TLD 缓存相关方法
getTldCache(): { tlds: string[]; updatedAt: number } | null {
try {
if (!this.db) return null
const row = this.db.prepare('SELECT tlds, updated_at FROM tld_cache WHERE id = 1').get() as { tlds: string; updated_at: number } | undefined
if (row) {
return {
tlds: JSON.parse(row.tlds),
updatedAt: row.updated_at
}
}
return null
} catch (e) {
console.error('获取 TLD 缓存失败:', e)
return null
}
}
setTldCache(tlds: string[]): void {
try {
if (!this.db) return
const now = Date.now()
this.db.prepare(`
INSERT OR REPLACE INTO tld_cache (id, tlds, updated_at) VALUES (1, ?, ?)
`).run(JSON.stringify(tlds), now)
} catch (e) {
console.error('设置 TLD 缓存失败:', e)
}
}
// AI 配置便捷方法
getAICurrentProvider(): string {
return this.get('aiCurrentProvider')
}
setAICurrentProvider(provider: string): void {
this.set('aiCurrentProvider', provider)
}
getAIProviderConfig(providerId: string): { apiKey: string; model: string; baseURL?: string } | null {
const configs = this.get('aiProviderConfigs')
return configs[providerId] || null
}
setAIProviderConfig(providerId: string, config: { apiKey: string; model: string; baseURL?: string }): void {
const configs = this.get('aiProviderConfigs')
configs[providerId] = config
this.set('aiProviderConfigs', configs)
}
getAllAIProviderConfigs(): { [providerId: string]: { apiKey: string; model: string; baseURL?: string } } {
return this.get('aiProviderConfigs')
}
}