fix:基本实现相关功能

This commit is contained in:
2026-01-08 00:12:19 +08:00
commit f361a7027b
68 changed files with 10920 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
import { END, START, StateGraph } from '@langchain/langgraph'
import { ReadingReflectionState } from '../state/readingReflectionState'
import { generateReadingReflectionContentNode } from '../nodes/readingReflectionContent'
import { generateReadingReflectionSummaryNode } from '../nodes/readingReflectionSummary'
const workflow = new StateGraph(ReadingReflectionState)
.addNode('generateReadingReflectionContent', generateReadingReflectionContentNode)
.addNode('generateReadingReflectionSummary', generateReadingReflectionSummaryNode)
.addEdge(START, 'generateReadingReflectionContent') // 开始 -> 生成正文
.addEdge('generateReadingReflectionContent', 'generateReadingReflectionSummary') // 正文生成后 -> 生成摘要
.addEdge('generateReadingReflectionSummary', END) // 摘要生成后 -> 结束
export const readingReflectionGraph = workflow.compile()

View File

@@ -0,0 +1,24 @@
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 })
export const createChatModel = (type: 'reading' | 'summary', schema: any) => {
const config = store.get(`chatModels.${type}`) as any
console.log('chatModels', config)
if (!config || !config.apiKey) {
throw new Error(`请先在设置中配置 ${type === 'reading' ? '心得' : '总结'} 模型的 API Key`)
}
return new ChatOpenAI({
apiKey: config.apiKey,
configuration: {
baseURL: config.baseURL || 'https://api.openai.com/v1'
},
modelName: config.modelName,
temperature: config.temperature
}).withStructuredOutput(schema)
}

View File

@@ -0,0 +1,55 @@
import { PromptTemplate } from '@langchain/core/prompts'
import { ReadingReflectionState } from '../state/readingReflectionState'
import { REFLECTION_CONTENT_PROMPT } from '@main/services/ai/prompts/readingReflactionPrompts'
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'
export const generateReadingReflectionContentNode = async (
state: typeof ReadingReflectionState.State,
config?: any
) => {
const taskId = config?.configurable?.thread_id
const repo = AppDataSource.getRepository(ReadingReflectionTaskItem)
const prompt = PromptTemplate.fromTemplate(REFLECTION_CONTENT_PROMPT)
const ReadingReflectionContentSchema = z.object({
title: z.string().describe('标题'),
content: z.string().describe('正文')
})
const contentModel = createChatModel('reading', ReadingReflectionContentSchema)
const chain = prompt.pipe(contentModel)
// 针对不同职业的微调逻辑(可选)
const occupationLabel =
{
student: '学生',
teacher: '教师',
professional: '职场人士',
researcher: '科研工作者',
other: '专业人士'
}[state.occupation] || state.occupation
const res = (await chain.invoke({
occupation: occupationLabel,
bookName: state.bookName,
author: state.author || '佚名',
description: state.description,
tone: state.tone || '温暖且理性',
wordCount: state.wordCount || 1000
})) as { title: string; content: string }
// 节点内部直接更新数据库状态
if (taskId) {
await repo.update(taskId, {
status: 'WRITING',
title: res.title,
content: res.content,
progress: 60
})
}
return { title: res.title, content: res.content }
}

View File

@@ -0,0 +1,46 @@
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'
/**
* 步骤 3生成摘要和关键词
* 该节点会接收上一个节点生成的 title 和 content
*/
export const generateReadingReflectionSummaryNode = async (
state: typeof ReadingReflectionState.State
) => {
const prompt = PromptTemplate.fromTemplate(REFLECTION_SUMMARY_PROMPT)
// 定义输出的结构
const summarySchema = z.object({
summary: z.string().describe('100字以内的摘要'),
keywords: z.array(z.string()).describe('3-5个关键词')
})
// const summaryModel = new ChatOpenAI({
// apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
// configuration: {
// baseURL: 'https://apis.iflow.cn/v1'
// },
// modelName: 'deepseek-v3.2',
// temperature: 0.3
// }).withStructuredOutput(summarySchema)
const summaryModel = createChatModel('summary', summarySchema)
const chain = prompt.pipe(summaryModel)
const res = (await chain.invoke({
title: state.title,
content: state.content
})) as {
summary: string
keywords: string[]
}
// 返回的结果会自动合并到 ReflectionState 中
return {
summary: res.summary,
keywords: res.keywords
}
}

View File

@@ -0,0 +1,61 @@
export const REFLECTION_CONTENT_PROMPT = `
# Role
你是一位在 {occupation} 领域拥有20年深厚资历的专家。你文笔{tone},擅长将理论书籍的核心观点与真实的行业场景深度结合。
# Goal
请根据提供的书籍信息,以专业视角撰写一份高质量的读书心得。
# Constraints & Rules
1. **禁止身份陈述 (No Self-Introduction)**
- **严禁**在开头或正文任何地方出现“作为一名...”、“我有...经验”、“从教/从业二十年”等自报家门的句子。
- 你的专业性应通过文字深度、对行业痛点的理解和专业术语自然流露,而不是通过宣称身份。
2. **场景化切入 (Direct Opening)**
- 开篇请直接从书籍的某个核心观点、一个具体的职场痛点、或一个生动的行业场景切入。
- 例如:不要说“作为医生我经常看到...”,而要说“当走廊的灯光亮起,面对那些复杂的病例时,我常在想...”
3. **职业化表达 (Professionalism)**
- 结合该职业特有的工作场景,将书籍内容转化为可操作的洞察。
4. **输出限制**
- 必须严格按照 JSON 格式输出,不含任何 Markdown 代码块标签或解释。
# Context Data
- 职业背景:{occupation}
- 书籍名称:{bookName}
- 作者:{author}
- 书籍描述:{description}
- 语气风格:{tone}
- 目标字数:{wordCount}
# Output JSON Format
{{
"title": "此处填写读书心得标题",
"content": "此处填写读书心得正文内容"
}}
`
export const REFLECTION_SUMMARY_PROMPT = `
# Role
你是一位金牌图书营销编辑,擅长撰写能够瞬间抓住读者眼球的“内容提要”和“搜索关键词”。你对文字有着极高的敏感度,能够从冗长的感悟中精准钩织出书籍的核心灵魂。
# Goal
基于提供的【读书心得正文】,提炼出一段极具感染力的摘要,并提取能精准覆盖内容核心的关键词。
# Constraints & Rules
1. **摘要编写要求 (Summary)**:
- **拒绝平铺直叙**:不要使用“本文介绍了...”这类陈旧开场,直接切入感悟的核心点。
- **受众共鸣**:摘要应体现出书籍对读者的实际启发或职场/生活改变。
- **精炼控制**:字数严格控制在 80-100 字之间,语言优美、有力。
2. **关键词提取要求 (Keywords)**:
- **多维覆盖**:必须包含 3-5 个词。
- **权重分配**1个书籍核心理念词1-2个职业/场景词幼儿教育、职业成长1个情感/启发词。
3. **输出限制**:
- 必须且只能输出标准 JSON 格式,严禁任何正文外的解释说明。
# Context Data
- 读书心得标题:{title}
- 读书心得正文:{content}
# Output JSON Format
{{
"summary": "此处填写精炼且感人的摘要内容",
"keywords": ["关键词1", "关键词2", "关键词3"]
}}
`

View File

@@ -0,0 +1,36 @@
import { Annotation } from '@langchain/langgraph'
import { Occupation } from '@shared/types/reflections'
export const ReadingReflectionState = Annotation.Root({
// 输入任务
bookName: Annotation<string>(),
author: Annotation<string | undefined>(),
description: Annotation<string>(),
occupation: Annotation<Occupation>(),
tone: Annotation<string | undefined>(),
wordCount: Annotation<number>(),
// 标题:使用简单的覆盖逻辑
title: Annotation<string>({
reducer: (_oldValue, newValue) => newValue,
default: () => ''
}),
// 内容
content: Annotation<string>({
reducer: (_oldValue, newValue) => newValue,
default: () => ''
}),
// 摘要
summary: Annotation<string>({
reducer: (_oldValue, newValue) => newValue,
default: () => ''
}),
// 关键词:数组通常使用追加或替换逻辑
keywords: Annotation<string[]>({
reducer: (_oldValue, newValue) => newValue,
default: () => []
})
})