feat(desktop): 实现MD文件模板导出方式

This commit is contained in:
2026-01-11 22:54:46 +08:00
parent cb7a1ba6d8
commit 3f7347427e
19 changed files with 857 additions and 180 deletions

View File

@@ -9,6 +9,9 @@ import { IUserReadingPersona } from '@shared/types/IUserReadingPersona'
export class PersonaService {
constructor(private personaRepo: Repository<ReadingPersona>) {}
/**
* 刷新画像并保存到数据库
*/
/**
* 刷新画像并保存到数据库
*/
@@ -16,22 +19,30 @@ export class PersonaService {
items: IReadingReflectionTaskItem[],
batches: IReadingReflectionTaskBatch[]
) {
const rawResult = await this.calculatePersona(items, batches) // 调用你原来的计算逻辑
// 1. 获取计算结果
const rawResult = await this.calculatePersona(items, batches)
// 2. 创建或更新实体
const persona = new ReadingPersona()
persona.id = 'current_user_persona'
// 核心分值映射
persona.cognition = rawResult.cognition
persona.breadth = rawResult.breadth
persona.practicality = rawResult.practicality
persona.output = rawResult.output
persona.global = rawResult.global
persona.topKeywords = JSON.stringify(rawResult.topKeywords)
// 存储完整的 stats 结构以便前端适配
// ✨ 修复关键点:从 rawResult.stats 中获取 topKeywords
// 因为 calculatePersona 返回的是 { stats: { topKeywords: [...] } }
persona.topKeywords = JSON.stringify(rawResult.stats.topKeywords)
// 3. 存储完整的 rawStats 结构,确保与前端接口定义对齐
persona.rawStats = {
totalWords: items.reduce((sum, i) => sum + (i.content?.length || 0), 0),
totalBooks: batches.length,
topKeywords: rawResult.topKeywords
totalWords: rawResult.stats.totalWords,
totalBooks: rawResult.stats.totalBooks,
totalHours: rawResult.stats.totalHours, // 别忘了我们在 calculatePersona 补充的专注时长
topKeywords: rawResult.stats.topKeywords
}
return await this.personaRepo.save(persona)
@@ -43,22 +54,44 @@ export class PersonaService {
items: IReadingReflectionTaskItem[],
batches: IReadingReflectionTaskBatch[]
) {
// 1. 计算认知深度:根据关键词频次
const totalBooks = batches.length
const totalWords = items.reduce((sum, i) => sum + (i.content?.length || 0), 0)
// --- 1. 认知深度 (Cognition) ---
const allKeywords = items.flatMap((i) => i.keywords || [])
const keywordMap = new Map<string, number>()
allKeywords.forEach((k) => keywordMap.set(k, (keywordMap.get(k) || 0) + 1))
const cognitionScore = Math.min(100, keywordMap.size * 1.5 + allKeywords.length / 8)
// 逻辑:去重后的关键词越多且重复越高,分值越高 (示例算法)
const cognitionScore = Math.min(100, keywordMap.size * 2 + allKeywords.length / 5)
// --- 2. 知识广度 (Breadth) - 修复 TS2339 ---
// 逻辑:如果 batch 没分类,就看有多少个独立的高频关键词,这代表了涉及的主题广度
const uniqueThemes = new Set(batches.map((b) => (b as any).category).filter(Boolean))
// 2. 计算知识广度:根据书籍数量
const breadthScore = Math.min(100, batches.length * 10)
let breadthScore = 0
if (uniqueThemes.size > 0) {
// 如果有分类数据,按分类算
breadthScore = Math.min(100, uniqueThemes.size * 20 + totalBooks * 2)
} else {
// 如果没分类数据,按关键词覆盖面算(每 5 个独立关键词视为一个知识领域)
breadthScore = Math.min(100, (keywordMap.size / 5) * 15 + totalBooks * 2)
}
// 3. 计算产出效率:根据总字数
const totalWords = items.reduce((sum, i) => sum + (i.content?.length || 0), 0)
const outputScore = Math.min(100, totalWords / 500) // 每 5万字满分
// --- 3. 语言能力与全球化 (Global) ---
const langDist = items.reduce(
(acc, curr) => {
// 兼容处理:如果 curr.language 不存在则默认为 'zh'
const lang = (curr as any).language || 'zh'
acc[lang] = (acc[lang] || 0) + 1
return acc
},
{} as Record<string, number>
)
// 计算英文占比分:有英文记录就从 60 分起跳,最高 100
const globalScore = langDist['en'] ? Math.min(100, 60 + langDist['en'] * 5) : 50
// --- 4. 专注时长 (Total Hours) ---
const totalHours = Math.round((totalWords / 1000) * 1.5 + totalBooks)
// 4. 计算 Top 10 关键词
const sortedKeywords = [...keywordMap.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
@@ -67,10 +100,15 @@ export class PersonaService {
return {
cognition: Math.round(cognitionScore),
breadth: Math.round(breadthScore),
output: Math.round(outputScore),
practicality: 75, // 可根据 occupation 比例动态计算
global: 60, // 可根据 language 比例动态计算
topKeywords: sortedKeywords
output: Math.min(100, Math.round(totalWords / 500)),
practicality: 75,
global: Math.round(globalScore),
stats: {
totalWords,
totalBooks,
totalHours,
topKeywords: sortedKeywords
}
}
}
}