feat: 添加自定义系统提示词和风格选项,优化AI摘要功能

This commit is contained in:
ILoveBingLu
2026-03-02 03:29:40 +08:00
parent 6a3e4dc0e1
commit 846fbd60ca
15 changed files with 266 additions and 23 deletions

View File

@@ -7,7 +7,7 @@
**一款现代化的微信聊天记录查看与分析工具**
[![License](https://img.shields.io/badge/license-CC--BY--NC--SA--4.0-blue.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-2.2.8-green.svg)](package.json)
[![Version](https://img.shields.io/badge/version-2.2.9-green.svg)](package.json)
[![Platform](https://img.shields.io/badge/platform-Windows-0078D6.svg?logo=windows)]()
[![Electron](https://img.shields.io/badge/Electron-39-47848F.svg?logo=electron)]()
[![React](https://img.shields.io/badge/React-19-61DAFB.svg?logo=react)]()
@@ -315,10 +315,10 @@ export const useChatStore = create<ChatStore>((set) => ({
| 渠道 | 链接 |
|:---:|:---|
| 🌐 **官方网站** | [密语 CipherTalk](https://miyuapp.aiqji.com) |
| 🌐 **官方网站** | [密语 CipherTalk](https://miyu.aiqji.com) |
| 🐛 **问题反馈** | [GitHub Issues](https://github.com/ILoveBingLu/CipherTalk/issues) |
| 💬 **讨论交流** | [GitHub Discussions](https://github.com/ILoveBingLu/CipherTalk/discussions) |
| 📱 **Telegram 群组** | [加入群聊](https://t.me/+toZ7bY15IZo3NjVl) |
| 📱 **Telegram 群组** | [加入群聊](https://t.me/CipherTalkChat) |
| ⭐ **项目主页** | [GitHub Repository](https://github.com/ILoveBingLu/CipherTalk) |
</div>

View File

@@ -3197,7 +3197,11 @@ function registerIpcHandlers() {
apiKey: string
model: string
detail: 'simple' | 'normal' | 'detailed'
systemPromptPreset?: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
customSystemPrompt?: string
customRequirement?: string
sessionName?: string
enableThinking?: boolean
}) => {
try {
const { aiService } = await import('./services/ai/aiService')
@@ -3280,7 +3284,11 @@ function registerIpcHandlers() {
apiKey: options.apiKey,
model: options.model,
detail: options.detail,
customRequirement: options.customRequirement
systemPromptPreset: options.systemPromptPreset,
customSystemPrompt: options.customSystemPrompt,
customRequirement: options.customRequirement,
sessionName: options.sessionName,
enableThinking: options.enableThinking
},
(chunk: string) => {
// 发送流式数据到渲染进程

View File

@@ -400,6 +400,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
apiKey: string
model: string
detail: 'simple' | 'normal' | 'detailed'
systemPromptPreset?: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
customSystemPrompt?: string
customRequirement?: string
sessionName?: string
enableThinking?: boolean
}) => ipcRenderer.invoke('ai:generateSummary', sessionId, timeRange, options),
onSummaryChunk: (callback: (chunk: string) => void) => {
ipcRenderer.on('ai:summaryChunk', (_, chunk) => callback(chunk))

View File

@@ -28,6 +28,8 @@ export interface SummaryOptions {
model?: string
language?: 'zh' | 'en'
detail?: 'simple' | 'normal' | 'detailed'
systemPromptPreset?: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
customSystemPrompt?: string
customRequirement?: string // 用户自定义要求
sessionName?: string // 会话名称
enableThinking?: boolean // 是否启用思考模式(推理模式)
@@ -163,7 +165,12 @@ class AIService {
/**
* 获取系统提示词
*/
private getSystemPrompt(language: string = 'zh', detail: string = 'normal'): string {
private getSystemPrompt(
language: string = 'zh',
detail: string = 'normal',
preset: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom' = 'default',
customSystemPrompt?: string
): string {
const detailInstructions = {
simple: '生成极简摘要,字数控制在 100 字以内。只保留最核心的事件和结论,忽略寒暄和琐碎细节。',
normal: '生成内容适中的摘要。涵盖对话主要话题、关键信息点及明确的约定事项。',
@@ -176,7 +183,7 @@ class AIService {
detailed: '深度详尽'
}
return `### 角色定义
const basePrompt = `### 角色定义
你是一位拥有 10 年经验的高级情报分析师和沟通专家,擅长从琐碎、碎片化的聊天记录中精准提取高价值信息。
### 任务描述
@@ -210,6 +217,24 @@ ${detailInstructions[detail as keyof typeof detailInstructions] || detailInstruc
---
*注:若对应部分无相关内容,请直接忽略该标题。*`
const presetInstructionMap: Record<string, string> = {
'default': '保持通用摘要风格,兼顾信息完整性与可读性。',
'decision-focus': '重点提取所有决策、结论、拍板事项。若有意见分歧,请明确分歧点和最终取舍。',
'action-focus': '重点提取可执行事项:负责人、截止时间、前置依赖、下一步动作。尽量转写为清单。',
'risk-focus': '重点提取风险、阻塞、争议、潜在误解及其影响范围,并给出可执行的缓解建议。'
}
if (preset === 'custom') {
const custom = (customSystemPrompt || '').trim()
if (custom) {
return `${basePrompt}\n\n### 用户自定义系统提示词\n${custom}`
}
return `${basePrompt}\n\n### 提示\n当前选择了自定义系统提示词但内容为空。请按默认规则输出。`
}
const presetInstruction = presetInstructionMap[preset] || presetInstructionMap.default
return `${basePrompt}\n\n### 风格偏好\n${presetInstruction}`
}
/**
@@ -433,7 +458,14 @@ ${detailInstructions[detail as keyof typeof detailInstructions] || detailInstruc
const formattedMessages = this.formatMessages(messages, contacts, options.sessionId)
// 构建提示词
const systemPrompt = this.getSystemPrompt(options.language, options.detail)
const presetFromConfig = (this.configService.get('aiSystemPromptPreset') as any) || 'default'
const customSystemPromptFromConfig = (this.configService.get('aiCustomSystemPrompt') as string) || ''
const systemPrompt = this.getSystemPrompt(
options.language,
options.detail,
options.systemPromptPreset || presetFromConfig,
options.customSystemPrompt ?? customSystemPromptFromConfig
)
// 使用会话名称优化提示词
const targetName = options.sessionName || options.sessionId

View File

@@ -64,6 +64,8 @@ interface ConfigSchema {
}
aiDefaultTimeRange: number
aiSummaryDetail: 'simple' | 'normal' | 'detailed'
aiSystemPromptPreset: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
aiCustomSystemPrompt: string
aiEnableCache: boolean
aiEnableThinking: boolean // 是否显示思考过程
aiMessageLimit: number // 摘要提取的消息条数限制
@@ -102,6 +104,8 @@ const defaults: ConfigSchema = {
aiProviderConfigs: {}, // 空对象,用户配置后填充
aiDefaultTimeRange: 7, // 默认7天
aiSummaryDetail: 'normal',
aiSystemPromptPreset: 'default',
aiCustomSystemPrompt: '',
aiEnableCache: true,
aiEnableThinking: true, // 默认显示思考过程
aiMessageLimit: 3000 // 默认3000条用户可调至5000
@@ -199,6 +203,31 @@ export class ConfigService {
} catch (e) {
console.error('迁移 AI 配置失败:', e)
}
// 迁移:兼容旧字段 baseUrl -> baseURL
try {
const aiConfigsRow = this.db.prepare("SELECT value FROM config WHERE key = 'aiProviderConfigs'").get() as { value: string } | undefined
if (aiConfigsRow) {
const aiConfigs = JSON.parse(aiConfigsRow.value || '{}') as Record<string, any>
let changed = false
for (const providerId of Object.keys(aiConfigs)) {
const cfg = aiConfigs[providerId]
if (cfg && !cfg.baseURL && cfg.baseUrl) {
cfg.baseURL = cfg.baseUrl
delete cfg.baseUrl
changed = true
}
}
if (changed) {
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run('aiProviderConfigs', JSON.stringify(aiConfigs))
console.log('[Config] AI 提供商配置字段已迁移: baseUrl -> baseURL')
}
}
} catch (e) {
console.error('迁移 AI baseURL 字段失败:', e)
}
} catch (e) {
console.error('初始化配置数据库失败:', e)
}
@@ -313,8 +342,16 @@ export class ConfigService {
}
getAIProviderConfig(providerId: string): { apiKey: string; model: string; baseURL?: string } | null {
const configs = this.get('aiProviderConfigs')
return configs[providerId] || null
const configs = this.get('aiProviderConfigs') as any
const providerConfig = configs?.[providerId]
if (!providerConfig) return null
// 兼容历史字段 baseUrl
if (!providerConfig.baseURL && providerConfig.baseUrl) {
providerConfig.baseURL = providerConfig.baseUrl
}
return providerConfig
}
setAIProviderConfig(providerId: string, config: { apiKey: string; model: string; baseURL?: string }): void {

View File

@@ -1,6 +1,6 @@
{
"name": "ciphertalk",
"version": "2.2.8",
"version": "2.2.9",
"description": "密语 - 微信聊天记录查看工具",
"author": "ILoveBingLu",
"license": "CC-BY-NC-SA-4.0",

View File

@@ -12,7 +12,7 @@ function WhatsNewModal({ onClose, version }: WhatsNewModalProps) {
{
icon: <Package size={20} />,
title: '优化',
desc: '优化图片密钥获取。'
desc: '优化AI摘要。'
},
// {
// icon: <Image size={20} />,
@@ -29,11 +29,11 @@ function WhatsNewModal({ onClose, version }: WhatsNewModalProps) {
// title: '分类导出',
// desc: '导出时可按群聊或个人聊天筛选,支持日期范围过滤。'
// }
// {
// icon: <Aperture size={20} />,
// title: '朋友圈',
// desc: '评论内的表情包已完成解密!'
// }
{
icon: <Aperture size={20} />,
title: '朋友圈',
desc: '评论内的图片已完成解密!'
}
]
const handleTelegram = () => {

View File

@@ -238,6 +238,25 @@
}
}
.custom-system-prompt-textarea {
width: 100%;
min-height: 160px;
resize: vertical;
padding: 12px 14px;
border: 1px solid var(--border-color);
border-radius: 12px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 13px;
line-height: 1.6;
outline: none;
transition: border-color 0.2s;
&:focus {
border-color: var(--primary);
}
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;

View File

@@ -103,6 +103,10 @@ interface AISummarySettingsProps {
setDefaultTimeRange: (val: number) => void
summaryDetail: 'simple' | 'normal' | 'detailed'
setSummaryDetail: (val: 'simple' | 'normal' | 'detailed') => void
systemPromptPreset: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
setSystemPromptPreset: (val: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom') => void
customSystemPrompt: string
setCustomSystemPrompt: (val: string) => void
enableThinking: boolean
setEnableThinking: (val: boolean) => void
messageLimit: number
@@ -121,6 +125,10 @@ function AISummarySettings({
setDefaultTimeRange,
summaryDetail,
setSummaryDetail,
systemPromptPreset,
setSystemPromptPreset,
customSystemPrompt,
setCustomSystemPrompt,
enableThinking,
setEnableThinking,
messageLimit,
@@ -331,6 +339,13 @@ function AISummarySettings({
{ value: 7, label: '最近 7 天' },
{ value: 30, label: '最近 30 天' }
]
const systemPromptPresetOptions = [
{ value: 'default', label: '通用平衡(默认)' },
{ value: 'decision-focus', label: '决策优先(重点提炼结论)' },
{ value: 'action-focus', label: '行动优先(重点提炼待办)' },
{ value: 'risk-focus', label: '风险优先(重点识别阻塞与风险)' },
{ value: 'custom', label: '自定义系统提示词' }
]
return (
<div className="tab-content ai-summary-settings">
@@ -558,6 +573,37 @@ function AISummarySettings({
</div>
</div>
<h3 className="section-title"></h3>
<div className="settings-form" style={{ marginTop: '8px' }}>
<div className="form-group">
<label></label>
<CustomSelect
value={systemPromptPreset}
onChange={setSystemPromptPreset}
options={systemPromptPresetOptions}
/>
<div className="form-hint">
使
</div>
</div>
{systemPromptPreset === 'custom' && (
<div className="form-group">
<label></label>
<textarea
className="custom-system-prompt-textarea"
placeholder="例如:你是一名项目经理助手。请优先输出任务清单,按负责人和截止时间分组。"
value={customSystemPrompt}
onChange={(e) => setCustomSystemPrompt(e.target.value)}
rows={8}
/>
<div className="form-hint">
退
</div>
</div>
)}
</div>
{/* 4. 使用统计 */}
{usageStats && (
<>

View File

@@ -143,7 +143,7 @@ function AISummaryWindow() {
try {
// 检查 API 配置 - 使用新的配置服务
const { getAiApiKey, getAiProvider, getAiModel, getAiSummaryDetail, getAiEnableThinking } = await import('../services/config')
const { getAiApiKey, getAiProvider, getAiModel, getAiSummaryDetail, getAiEnableThinking, getAiSystemPromptPreset, getAiCustomSystemPrompt } = await import('../services/config')
const apiKey = await getAiApiKey()
console.log('[AISummaryWindow] 当前 API Key:', apiKey ? '已配置' : '未配置', '长度:', apiKey?.length)
@@ -159,8 +159,10 @@ function AISummaryWindow() {
const model = await getAiModel()
const detail = await getAiSummaryDetail()
const enableThinking = await getAiEnableThinking()
const systemPromptPreset = await getAiSystemPromptPreset()
const customSystemPrompt = await getAiCustomSystemPrompt()
console.log('[AISummaryWindow] 配置信息:', { provider, model, detail, enableThinking })
console.log('[AISummaryWindow] 配置信息:', { provider, model, detail, enableThinking, systemPromptPreset })
// 监听流式输出
let internalThinkMode = false
@@ -225,6 +227,8 @@ function AISummaryWindow() {
apiKey: apiKey as string,
model: model || 'glm-4.5-flash',
detail: detail || 'normal',
systemPromptPreset,
customSystemPrompt,
customRequirement: customRequirement,
sessionName: sessionName,
enableThinking: enableThinking !== false // 默认启用

View File

@@ -132,6 +132,8 @@ function SettingsPage() {
const [aiModel, setAiModelState] = useState('')
const [aiDefaultTimeRange, setAiDefaultTimeRangeState] = useState<number>(7)
const [aiSummaryDetail, setAiSummaryDetailState] = useState<'simple' | 'normal' | 'detailed'>('normal')
const [aiSystemPromptPreset, setAiSystemPromptPresetState] = useState<'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'>('default')
const [aiCustomSystemPrompt, setAiCustomSystemPromptState] = useState<string>('')
const [aiEnableThinking, setAiEnableThinkingState] = useState<boolean>(true)
const [aiMessageLimit, setAiMessageLimitState] = useState<number>(3000)
@@ -205,6 +207,8 @@ function SettingsPage() {
const savedAiModel = await configService.getAiModel()
const savedAiDefaultTimeRange = await configService.getAiDefaultTimeRange()
const savedAiSummaryDetail = await configService.getAiSummaryDetail()
const savedAiSystemPromptPreset = await configService.getAiSystemPromptPreset()
const savedAiCustomSystemPrompt = await configService.getAiCustomSystemPrompt()
const savedAiEnableThinking = await configService.getAiEnableThinking()
const savedAiMessageLimit = await configService.getAiMessageLimit()
@@ -213,6 +217,8 @@ function SettingsPage() {
setAiModelState(savedAiModel)
setAiDefaultTimeRangeState(savedAiDefaultTimeRange)
setAiSummaryDetailState(savedAiSummaryDetail)
setAiSystemPromptPresetState(savedAiSystemPromptPreset)
setAiCustomSystemPromptState(savedAiCustomSystemPrompt)
setAiEnableThinkingState(savedAiEnableThinking)
setAiMessageLimitState(savedAiMessageLimit)
} catch (e) {
@@ -722,6 +728,8 @@ function SettingsPage() {
await configService.setAiModel(aiModel)
await configService.setAiDefaultTimeRange(aiDefaultTimeRange)
await configService.setAiSummaryDetail(aiSummaryDetail)
await configService.setAiSystemPromptPreset(aiSystemPromptPreset)
await configService.setAiCustomSystemPrompt(aiCustomSystemPrompt)
await configService.setAiEnableThinking(aiEnableThinking)
await configService.setAiMessageLimit(aiMessageLimit)
@@ -2593,6 +2601,10 @@ function SettingsPage() {
setDefaultTimeRange={setAiDefaultTimeRangeState}
summaryDetail={aiSummaryDetail}
setSummaryDetail={setAiSummaryDetailState}
systemPromptPreset={aiSystemPromptPreset}
setSystemPromptPreset={setAiSystemPromptPresetState}
customSystemPrompt={aiCustomSystemPrompt}
setCustomSystemPrompt={setAiCustomSystemPromptState}
enableThinking={aiEnableThinking}
setEnableThinking={setAiEnableThinkingState}
messageLimit={aiMessageLimit}

View File

@@ -342,7 +342,15 @@ export async function setAiProvider(provider: string): Promise<void> {
export async function getAiProviderConfig(providerId: string): Promise<{ apiKey: string; model: string; baseURL?: string } | null> {
const configs = await config.get('aiProviderConfigs')
const allConfigs = (configs as any) || {}
return allConfigs[providerId] || null
const providerConfig = allConfigs[providerId]
if (!providerConfig) return null
// 兼容旧字段 baseUrl
if (!providerConfig.baseURL && providerConfig.baseUrl) {
providerConfig.baseURL = providerConfig.baseUrl
}
return providerConfig
}
// 设置指定提供商的配置
@@ -372,7 +380,8 @@ export async function setAiApiKey(key: string): Promise<void> {
const existingConfig = await getAiProviderConfig(currentProvider)
await setAiProviderConfig(currentProvider, {
apiKey: key,
model: existingConfig?.model || ''
model: existingConfig?.model || '',
baseURL: existingConfig?.baseURL
})
}
@@ -389,7 +398,8 @@ export async function setAiModel(model: string): Promise<void> {
const existingConfig = await getAiProviderConfig(currentProvider)
await setAiProviderConfig(currentProvider, {
apiKey: existingConfig?.apiKey || '',
model: model
model: model,
baseURL: existingConfig?.baseURL
})
}
@@ -415,6 +425,28 @@ export async function setAiSummaryDetail(detail: 'simple' | 'normal' | 'detailed
await config.set('aiSummaryDetail', detail)
}
// 获取系统提示词模板
export async function getAiSystemPromptPreset(): Promise<'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'> {
const value = await config.get('aiSystemPromptPreset')
return (value as 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom') || 'default'
}
// 设置系统提示词模板
export async function setAiSystemPromptPreset(preset: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'): Promise<void> {
await config.set('aiSystemPromptPreset', preset)
}
// 获取自定义系统提示词
export async function getAiCustomSystemPrompt(): Promise<string> {
const value = await config.get('aiCustomSystemPrompt')
return (value as string) || ''
}
// 设置自定义系统提示词
export async function setAiCustomSystemPrompt(prompt: string): Promise<void> {
await config.set('aiCustomSystemPrompt', prompt || '')
}
// 获取是否启用思考模式
export async function getAiEnableThinking(): Promise<boolean> {
const value = await config.get('aiEnableThinking')

View File

@@ -1,6 +1,6 @@
import { create } from 'zustand'
export type ThemeId = 'cloud-dancer' | 'corundum-blue' | 'kiwi-green' | 'spicy-red' | 'teal-water' | 'new-year'
export type ThemeId = 'cloud-dancer' | 'corundum-blue' | 'kiwi-green' | 'spicy-red' | 'teal-water' | 'new-year' | 'sakura-mist'
export type ThemeMode = 'light' | 'dark' | 'system'
export type AppIcon = 'default' | 'xinnian'
@@ -54,6 +54,13 @@ export const themes: ThemeInfo[] = [
description: 'Happy New Year 2026',
primaryColor: '#E60012',
bgColor: '#FFF0F0'
},
{
id: 'sakura-mist',
name: '樱雾粉',
description: '温柔、治愈的高级粉',
primaryColor: '#D86A8A',
bgColor: '#FFF2F7'
}
]

View File

@@ -164,6 +164,26 @@
--logo-invert: 0;
}
// 樱雾粉主题
[data-theme="sakura-mist"][data-mode="light"],
[data-theme="sakura-mist"]:not([data-mode]) {
--primary: #D86A8A;
--primary-hover: #C55B7A;
--primary-light: rgba(216, 106, 138, 0.12);
--bg-primary: #FFF2F7;
--bg-secondary: rgba(255, 255, 255, 0.82);
--bg-tertiary: rgba(216, 106, 138, 0.05);
--bg-hover: rgba(216, 106, 138, 0.08);
--text-primary: #4B2D38;
--text-secondary: #7A5865;
--text-tertiary: #A78492;
--border-color: rgba(216, 106, 138, 0.16);
--bg-gradient: linear-gradient(135deg, #FFF2F7 0%, #FFE6F0 100%);
--primary-gradient: linear-gradient(135deg, #D86A8A 0%, #F08FB1 100%);
--card-bg: rgba(255, 255, 255, 0.82);
--logo-invert: 0;
}
// ==================== 深色主题 ====================
// 云上舞白 - 深色
@@ -280,6 +300,25 @@
--logo-invert: 1;
}
// 樱雾粉 - 深色
[data-theme="sakura-mist"][data-mode="dark"] {
--primary: #F08FB1;
--primary-hover: #F6A3C0;
--primary-light: rgba(240, 143, 177, 0.18);
--bg-primary: #24161D;
--bg-secondary: rgba(44, 30, 38, 0.92);
--bg-tertiary: rgba(255, 255, 255, 0.06);
--bg-hover: rgba(255, 255, 255, 0.1);
--text-primary: #FFEAF2;
--text-secondary: #DDBAC8;
--text-tertiary: #AC8796;
--border-color: rgba(255, 255, 255, 0.12);
--bg-gradient: linear-gradient(135deg, #24161D 0%, #34212A 100%);
--primary-gradient: linear-gradient(135deg, #D86A8A 0%, #F08FB1 100%);
--card-bg: rgba(44, 30, 38, 0.92);
--logo-invert: 1;
}
// 重置样式
* {
margin: 0;
@@ -384,4 +423,4 @@ img {
&:hover {
text-decoration: underline;
}
}
}

View File

@@ -780,6 +780,8 @@ export interface ElectronAPI {
apiKey: string
model: string
detail: 'simple' | 'normal' | 'detailed'
systemPromptPreset?: 'default' | 'decision-focus' | 'action-focus' | 'risk-focus' | 'custom'
customSystemPrompt?: string
customRequirement?: string
sessionName?: string
enableThinking?: boolean