feat(desktop): 实现一些功能

1. 实现任务暂停功能

2. 实现页面的国际化功能

3.优化项目的结构以及BUG

4. 优化系统架构

5. 实现一大堆的功能
This commit is contained in:
2026-01-25 03:30:23 +08:00
parent 3f7347427e
commit 455dd1f4cd
88 changed files with 13451 additions and 581 deletions

View File

@@ -1,12 +1,9 @@
import { ChatOpenAI } from '@langchain/openai'
import Store from 'electron-store'
import { CONFIG_STORE_KEY } from '@rpc/constants/store_key'
const StoreClass = (Store as any).default || Store
const store = new StoreClass({ encryptionKey: CONFIG_STORE_KEY })
import { configService } from '@main/services/configService'
export const createChatModel = (type: 'reading' | 'summary', schema: any) => {
const config = store.get(`chatModels.${type}`) as any
const chatModelsConfig = configService.getChatModelsConfig()
const config = chatModelsConfig?.[type] as any
console.log('chatModels', config)
if (!config || !config.apiKey) {

View File

@@ -4,7 +4,7 @@ import { REFLECTION_CONTENT_PROMPT } from '@main/services/ai/prompts/readingRefl
import { z } from 'zod'
import { AppDataSource } from '@main/db/data-source'
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
import { createChatModel } from '@main/services/ai/llmService'
import { createChatModel } from '@main/services/ai/core/llmService'
export const generateReadingReflectionContentNode = async (
state: typeof ReadingReflectionState.State,

View File

@@ -2,7 +2,7 @@ import { PromptTemplate } from '@langchain/core/prompts'
import { ReadingReflectionState } from '../state/readingReflectionState'
import { REFLECTION_SUMMARY_PROMPT } from '@main/services/ai/prompts/readingReflactionPrompts'
import { z } from 'zod'
import { createChatModel } from '@main/services/ai/llmService'
import { createChatModel } from '@main/services/ai/core/llmService'
/**
* 步骤 3生成摘要和关键词

View File

@@ -0,0 +1,173 @@
import Store from 'electron-store'
import { app } from 'electron'
import path from 'path'
import crypto from 'crypto'
// 定义配置键名
const NOTIFICATION_KEY = 'notification'
const CHAT_MODELS_KEY = 'chatModels'
const LANGUAGE_KEY = 'language'
/**
* 配置服务,负责统一管理应用配置
*/
export class ConfigService {
private store: Store
private encryptionKey: string
constructor() {
// 生成或获取加密密钥
this.encryptionKey = this.getOrGenerateEncryptionKey()
// 兼容性处理获取 Store 构造函数
const StoreClass = (Store as any).default || Store
// 创建 Store 实例
this.store = new StoreClass({
encryptionKey: this.encryptionKey,
// 使用更安全的加密算法
schema: {
[NOTIFICATION_KEY]: {
type: 'object',
properties: {
masterSwitch: { type: 'boolean' },
taskCompleted: { type: 'boolean' },
taskFailed: { type: 'boolean' },
silentMode: { type: 'boolean' }
}
},
[CHAT_MODELS_KEY]: {
type: 'object',
properties: {
reading: { type: 'object' },
summary: { type: 'object' }
}
},
[LANGUAGE_KEY]: {
type: 'object',
properties: {
language: { type: 'string', enum: ['zh', 'en', 'ja', 'ko', 'es'] }
}
}
},
// 提高加密强度
encryptionAlgorithm: 'aes-256-cbc' as any
})
}
/**
* 生成或获取加密密钥
*/
private getOrGenerateEncryptionKey(): string {
// 从安全位置获取或生成密钥
// 这里实现一个简单的密钥生成和存储机制
const keyPath = path.join(app.getPath('userData'), 'encryption.key')
try {
// 尝试从文件读取密钥
const fs = require('fs')
if (fs.existsSync(keyPath)) {
return fs.readFileSync(keyPath, 'utf8')
}
// 如果密钥不存在,生成一个新的随机密钥
const newKey = crypto.randomBytes(32).toString('hex')
fs.writeFileSync(keyPath, newKey, { mode: 0o600 }) // 设置文件权限,只有所有者可以读写
return newKey
} catch (error) {
// 如果出现错误,使用一个基于应用信息的密钥作为后备
console.error('Failed to generate or read encryption key:', error)
return this.generateFallbackKey()
}
}
/**
* 生成后备密钥
*/
private generateFallbackKey(): string {
// 基于应用信息生成一个后备密钥
const appInfo = `${app.name}-${app.getVersion()}-${app.getPath('userData')}`
return crypto.createHash('sha256').update(appInfo).digest('hex')
}
/**
* 获取通知配置
*/
getNotificationConfig(): any {
return this.store.get(NOTIFICATION_KEY) || {
masterSwitch: true,
taskCompleted: true,
taskFailed: true
}
}
/**
* 保存通知配置
*/
saveNotificationConfig(config: any): void {
this.store.set(NOTIFICATION_KEY, config)
}
/**
* 获取聊天模型配置
*/
getChatModelsConfig(): any {
const data = this.store.get(CHAT_MODELS_KEY) as { reading?: any; summary?: any } | null
// 检查是否包含必要的嵌套 Key如果没有说明是旧版本数据
if (data && typeof data === 'object' && !data.reading && !data.summary) {
console.log('检测到旧版本配置,正在重置...')
this.store.delete(CHAT_MODELS_KEY) // 删除旧的根键
return null
}
return data || null
}
/**
* 保存聊天模型配置
*/
saveChatModelConfig(type: string, config: any): void {
this.store.set(`${CHAT_MODELS_KEY}.${type}`, config)
}
/**
* 获取指定键的配置
*/
get(key: string): any {
return this.store.get(key)
}
/**
* 设置配置
*/
set(key: string, value: any): void {
this.store.set(key, value)
}
/**
* 删除配置
*/
delete(key: string): void {
this.store.delete(key)
}
/**
* 获取语言配置
*/
getLanguageConfig(): any {
return this.store.get(LANGUAGE_KEY) || {
language: 'zh'
}
}
/**
* 保存语言配置
*/
saveLanguageConfig(config: any): void {
this.store.set(LANGUAGE_KEY, config)
}
}
// 导出单例实例
export const configService = new ConfigService()

View File

@@ -0,0 +1,56 @@
import { Notification } from 'electron'
import { configService } from '@main/services/configService'
import { notificationEventEmitter } from '@rpc/router/notice.router'
export class NotificationService {
/**
* 处理通知逻辑
*/
handleNotification(status: string, progress: number, total: number, index: number, batchId: string): void {
// 从配置服务获取用户偏好
const config = configService.getNotificationConfig()
// 如果总开关关闭,直接拦截
if (!config.masterSwitch) return
// 场景 A: 任务全部完成 (100%)
if (progress === 100 && config.taskCompleted) {
// 只有当所有子任务都完成,或者当前是单任务时才弹出
// 如果是批量任务,你可以选择在最后一个子任务完成时通知
if (index + 1 === total) {
const notification = new Notification({
title: '🎉 读书心得已生成',
body: total > 1 ? `${total} 篇心得已全部处理完成。` : '您的书籍心得已准备就绪。',
silent: config.silentMode
})
notification.show()
// 监听点击事件
notification.on('click', () => {
notificationEventEmitter.emit('notification-click', {
batchId
})
})
}
}
// 场景 B: 任务失败 (假设你传入的 status 是 'FAILED')
if (status === 'FAILED' && config.taskFailed) {
const notification = new Notification({
title: '❌ 任务生成失败',
body: `${index + 1} 项任务执行异常,请检查网络或 API 余额。`,
silent: config.silentMode
})
notification.show()
// 监听点击事件
notification.on('click', () => {
notificationEventEmitter.emit('notification-click', {
batchId
})
})
}
}
}