添加了记忆功能

This commit is contained in:
1600822305 2025-04-13 16:51:05 +08:00
parent 07bfb8e01e
commit 09e6871118
19 changed files with 2678 additions and 422 deletions

View File

@ -0,0 +1,195 @@
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { Box } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { addShortMemoryItem, analyzeAndAddShortMemories } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { deleteShortMemory } from '@renderer/store/memory'
import { Button, Empty, Input, List, Modal, Tooltip } from 'antd'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const { confirm } = Modal
const ButtonGroup = styled.div`
display: flex;
gap: 8px;
margin-top: 8px;
`
const MemoryContent = styled.div`
word-break: break-word;
`
interface ShowParams {
topicId: string
}
interface Props extends ShowParams {
resolve: (data: any) => void
}
const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const [open, setOpen] = useState(true)
// 获取短记忆状态
const shortMemoryActive = useAppSelector((state) => state.memory?.shortMemoryActive || false)
const shortMemories = useAppSelector((state) => {
const allShortMemories = state.memory?.shortMemories || []
// 只显示当前话题的短记忆
return topicId ? allShortMemories.filter((memory) => memory.topicId === topicId) : []
})
// 添加短记忆的状态
const [newMemoryContent, setNewMemoryContent] = useState('')
const [isAnalyzing, setIsAnalyzing] = useState(false)
// 添加新的短记忆
const handleAddMemory = () => {
if (newMemoryContent.trim() && topicId) {
addShortMemoryItem(newMemoryContent.trim(), topicId)
setNewMemoryContent('') // 清空输入框
}
}
// 手动分析对话内容
const handleAnalyzeConversation = async () => {
if (!topicId || !shortMemoryActive) return
setIsAnalyzing(true)
try {
const result = await analyzeAndAddShortMemories(topicId)
if (result) {
// 如果有新的短期记忆被添加
Modal.success({
title: t('settings.memory.shortMemoryAnalysisSuccess') || '分析成功',
content: t('settings.memory.shortMemoryAnalysisSuccessContent') || '已成功提取并添加重要信息到短期记忆'
})
} else {
// 如果没有新的短期记忆被添加
Modal.info({
title: t('settings.memory.shortMemoryAnalysisNoNew') || '无新信息',
content: t('settings.memory.shortMemoryAnalysisNoNewContent') || '未发现新的重要信息或所有信息已存在'
})
}
} catch (error) {
console.error('Failed to analyze conversation:', error)
Modal.error({
title: t('settings.memory.shortMemoryAnalysisError') || '分析失败',
content: t('settings.memory.shortMemoryAnalysisErrorContent') || '分析对话内容时出错'
})
} finally {
setIsAnalyzing(false)
}
}
// 删除短记忆
const handleDeleteMemory = (id: string) => {
confirm({
title: t('settings.memory.confirmDelete'),
icon: <ExclamationCircleOutlined />,
content: t('settings.memory.confirmDeleteContent'),
onOk() {
dispatch(deleteShortMemory(id))
}
})
}
const onClose = () => {
setOpen(false)
}
const afterClose = () => {
resolve({})
}
ShortMemoryPopup.hide = onClose
return (
<Modal
title={t('settings.memory.shortMemory')}
open={open}
onCancel={onClose}
afterClose={afterClose}
footer={null}
width={500}
centered>
<Box mb={16}>
<Input.TextArea
value={newMemoryContent}
onChange={(e) => setNewMemoryContent(e.target.value)}
placeholder={t('settings.memory.addShortMemoryPlaceholder')}
autoSize={{ minRows: 2, maxRows: 4 }}
disabled={!shortMemoryActive || !topicId}
/>
<ButtonGroup>
<Button
type="primary"
onClick={handleAddMemory}
disabled={!shortMemoryActive || !newMemoryContent.trim() || !topicId}>
{t('settings.memory.addShortMemory')}
</Button>
<Button onClick={handleAnalyzeConversation} loading={isAnalyzing} disabled={!shortMemoryActive || !topicId}>
{t('settings.memory.analyzeConversation') || '分析对话'}
</Button>
</ButtonGroup>
</Box>
<MemoriesList>
{shortMemories.length > 0 ? (
<List
itemLayout="horizontal"
dataSource={shortMemories}
renderItem={(memory) => (
<List.Item
actions={[
<Tooltip title={t('settings.memory.delete')} key="delete">
<Button
icon={<DeleteOutlined />}
onClick={() => handleDeleteMemory(memory.id)}
type="text"
danger
/>
</Tooltip>
]}>
<List.Item.Meta
title={<MemoryContent>{memory.content}</MemoryContent>}
description={new Date(memory.createdAt).toLocaleString()}
/>
</List.Item>
)}
/>
) : (
<Empty description={!topicId ? t('settings.memory.noCurrentTopic') : t('settings.memory.noShortMemories')} />
)}
</MemoriesList>
</Modal>
)
}
const MemoriesList = styled.div`
max-height: 300px;
overflow-y: auto;
`
const TopViewKey = 'ShortMemoryPopup'
export default class ShortMemoryPopup {
static hide: () => void = () => {}
static show(props: ShowParams) {
return new Promise<any>((resolve) => {
TopView.show(
<PopupContainer
{...props}
resolve={(v) => {
resolve(v)
TopView.hide(TopViewKey)
}}
/>,
TopViewKey
)
})
}
}

View File

@ -1047,12 +1047,74 @@
"analyzeNow": "Analyze Now",
"startingAnalysis": "Starting analysis...",
"cannotAnalyze": "Cannot analyze, please check settings",
"resetAnalyzingState": "Reset Analysis State",
"analyzeConversation": "Analyze Conversation",
"shortMemoryAnalysisSuccess": "Analysis Successful",
"shortMemoryAnalysisSuccessContent": "Successfully extracted and added important information to short-term memory",
"shortMemoryAnalysisNoNew": "No New Information",
"shortMemoryAnalysisNoNewContent": "No new important information found or all information already exists",
"shortMemoryAnalysisError": "Analysis Failed",
"shortMemoryAnalysisErrorContent": "Error occurred while analyzing conversation content",
"deduplication": {
"title": "Memory Deduplication",
"description": "Analyze similar memories in your memory library and provide intelligent merging suggestions.",
"selectList": "Select Memory List",
"allLists": "All Lists",
"selectTopic": "Select Topic",
"similarityThreshold": "Similarity Threshold",
"startAnalysis": "Start Analysis",
"help": "Help",
"helpTitle": "Memory Deduplication Help",
"helpContent1": "This feature analyzes similar memories in your memory library and provides merging suggestions.",
"helpContent2": "The similarity threshold determines how similar two memories need to be to be considered for merging. Higher values require more similarity.",
"helpContent3": "When you apply the results, similar memories will be merged into a new memory and the original memories will be deleted.",
"analyzing": "Analyzing...",
"noSimilarMemories": "No similar memories found",
"similarGroups": "Similar Memory Groups",
"group": "Group",
"items": "items",
"originalMemories": "Original Memories",
"mergedResult": "Merged Result",
"other": "Other",
"applyResults": "Apply Results",
"confirmApply": "Confirm Apply Results",
"confirmApplyContent": "Applying deduplication results will merge similar memories and delete the original ones. This action cannot be undone. Are you sure you want to continue?",
"applySuccess": "Applied Successfully",
"applySuccessContent": "Memory deduplication has been successfully applied"
},
"shortMemoryDeduplication": {
"title": "Short Memory Deduplication",
"description": "Analyze similar memories in your short-term memory and provide intelligent merging suggestions.",
"selectTopic": "Select Topic",
"similarityThreshold": "Similarity Threshold",
"startAnalysis": "Start Analysis",
"help": "Help",
"helpTitle": "Short Memory Deduplication Help",
"helpContent1": "This feature analyzes similar memories in your short-term memory and provides merging suggestions.",
"helpContent2": "The similarity threshold determines how similar two memories need to be to be considered for merging. Higher values require more similarity.",
"helpContent3": "When you apply the results, similar memories will be merged into a new memory and the original memories will be deleted.",
"analyzing": "Analyzing...",
"noSimilarMemories": "No similar memories found",
"similarGroups": "Similar Memory Groups",
"group": "Group",
"items": "items",
"originalMemories": "Original Memories",
"mergedResult": "Merged Result",
"other": "Other",
"applyResults": "Apply Results",
"confirmApply": "Confirm Apply Results",
"confirmApplyContent": "Applying deduplication results will merge similar memories and delete the original ones. This action cannot be undone. Are you sure you want to continue?",
"applySuccess": "Applied Successfully",
"applySuccessContent": "Short memory deduplication has been successfully applied"
},
"selectTopic": "Select Topic",
"selectTopicPlaceholder": "Select a topic to analyze",
"filterByCategory": "Filter by Category",
"allCategories": "All",
"uncategorized": "Uncategorized",
"shortMemory": "Short-term Memory",
"loading": "Loading...",
"longMemory": "Long-term Memory",
"toggleShortMemoryActive": "Toggle Short-term Memory",
"addShortMemory": "Add Short-term Memory",
"addShortMemoryPlaceholder": "Enter short-term memory content, only valid in current conversation",
@ -1060,22 +1122,7 @@
"noCurrentTopic": "Please select a conversation topic first",
"confirmDelete": "Confirm Delete",
"confirmDeleteContent": "Are you sure you want to delete this short-term memory?",
"delete": "Delete",
"memoryLists": "[to be translated]:记忆角色",
"listView": "[to be translated]:列表视图",
"mindmapView": "[to be translated]:思维导图",
"centerNodeLabel": "[to be translated]:用户记忆",
"addList": "[to be translated]:添加记忆列表",
"editList": "[to be translated]:编辑记忆列表",
"listName": "[to be translated]:列表名称",
"listNamePlaceholder": "[to be translated]:输入列表名称",
"listDescription": "[to be translated]:列表描述",
"listDescriptionPlaceholder": "[to be translated]:输入列表描述(可选)",
"noLists": "[to be translated]:暂无记忆列表",
"confirmDeleteList": "[to be translated]:确认删除列表",
"confirmDeleteListContent": "[to be translated]:确定要删除 {{name}} 列表吗?此操作将同时删除列表中的所有记忆,且不可恢复。",
"toggleActive": "[to be translated]:切换激活状态",
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。"
"delete": "Delete"
},
"mcp": {
"actions": "Actions",

View File

@ -1353,57 +1353,58 @@
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
},
"memory": {
"title": "[to be translated]:记忆功能",
"description": "[to be translated]:管理AI助手的长期记忆自动分析对话并提取重要信息",
"enableMemory": "[to be translated]:启用记忆功能",
"enableAutoAnalyze": "[to be translated]:启用自动分析",
"analyzeModel": "[to be translated]:分析模型",
"selectModel": "[to be translated]:选择模型",
"memoriesList": "[to be translated]:记忆列表",
"memoryLists": "[to be translated]:记忆角色",
"addMemory": "[to be translated]:添加记忆",
"editMemory": "[to be translated]:编辑记忆",
"clearAll": "[to be translated]:清空全部",
"noMemories": "[to be translated]:暂无记忆",
"memoryPlaceholder": "[to be translated]:输入要记住的内容",
"addSuccess": "[to be translated]:记忆添加成功",
"editSuccess": "[to be translated]:记忆编辑成功",
"deleteSuccess": "[to be translated]:记忆删除成功",
"clearSuccess": "[to be translated]:记忆清空成功",
"clearConfirmTitle": "[to be translated]:确认清空",
"clearConfirmContent": "[to be translated]:确定要清空所有记忆吗?此操作无法撤销。",
"listView": "[to be translated]:列表视图",
"mindmapView": "[to be translated]:思维导图",
"centerNodeLabel": "[to be translated]:用户记忆",
"manualAnalyze": "[to be translated]:手动分析",
"analyzeNow": "[to be translated]:立即分析",
"startingAnalysis": "[to be translated]:开始分析...",
"cannotAnalyze": "[to be translated]:无法分析,请检查设置",
"selectTopic": "[to be translated]:选择话题",
"selectTopicPlaceholder": "[to be translated]:选择要分析的话题",
"filterByCategory": "[to be translated]:按分类筛选",
"allCategories": "[to be translated]:全部",
"uncategorized": "[to be translated]:未分类",
"addList": "[to be translated]:添加记忆列表",
"editList": "[to be translated]:编辑记忆列表",
"listName": "[to be translated]:列表名称",
"listNamePlaceholder": "[to be translated]:输入列表名称",
"listDescription": "[to be translated]:列表描述",
"listDescriptionPlaceholder": "[to be translated]:输入列表描述(可选)",
"noLists": "[to be translated]:暂无记忆列表",
"confirmDeleteList": "[to be translated]:确认删除列表",
"confirmDeleteListContent": "[to be translated]:确定要删除 {{name}} 列表吗?此操作将同时删除列表中的所有记忆,且不可恢复。",
"toggleActive": "[to be translated]:切换激活状态",
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
"shortMemory": "[to be translated]:短期记忆",
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
"addShortMemory": "[to be translated]:添加短期记忆",
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
"noShortMemories": "[to be translated]:暂无短期记忆",
"noCurrentTopic": "[to be translated]:请先选择一个对话话题",
"confirmDelete": "[to be translated]:确认删除",
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
"delete": "[to be translated]:删除"
"title": "メモリー機能",
"description": "AIアシスタントの長期メモリーを管理し、会話を自動分析して重要な情報を抽出します",
"enableMemory": "メモリー機能を有効にする",
"enableAutoAnalyze": "自動分析を有効にする",
"analyzeModel": "分析モデル",
"selectModel": "モデルを選択",
"memoriesList": "メモリーリスト",
"memoryLists": "メモリーロール",
"addMemory": "メモリーを追加",
"editMemory": "メモリーを編集",
"clearAll": "すべてクリア",
"noMemories": "メモリーなし",
"memoryPlaceholder": "記憶したい内容を入力",
"addSuccess": "メモリーが正常に追加されました",
"editSuccess": "メモリーが正常に編集されました",
"deleteSuccess": "メモリーが正常に削除されました",
"clearSuccess": "メモリーが正常にクリアされました",
"clearConfirmTitle": "クリアの確認",
"clearConfirmContent": "すべてのメモリーをクリアしますか?この操作は元に戻せません。",
"listView": "リスト表示",
"mindmapView": "マインドマップ表示",
"centerNodeLabel": "ユーザーメモリー",
"manualAnalyze": "手動分析",
"analyzeNow": "今すぐ分析",
"startingAnalysis": "分析開始...",
"cannotAnalyze": "分析できません、設定を確認してください",
"selectTopic": "トピックを選択",
"selectTopicPlaceholder": "分析するトピックを選択",
"filterByCategory": "カテゴリーで絞り込み",
"allCategories": "すべて",
"uncategorized": "未分類",
"addList": "メモリーリストを追加",
"editList": "メモリーリストを編集",
"listName": "リスト名",
"listNamePlaceholder": "リスト名を入力",
"listDescription": "リストの説明",
"listDescriptionPlaceholder": "リストの説明を入力(オプション)",
"noLists": "メモリーリストなし",
"confirmDeleteList": "リスト削除の確認",
"confirmDeleteListContent": "{{name}} リストを削除しますか?この操作はリスト内のすべてのメモリーも削除し、元に戻せません。",
"toggleActive": "アクティブ状態を切り替え",
"clearConfirmContentList": "{{name}} のすべてのメモリーをクリアしますか?この操作は元に戻せません。",
"shortMemory": "短期メモリー",
"longMemory": "長期メモリー",
"toggleShortMemoryActive": "短期メモリー機能を切り替え",
"addShortMemory": "短期メモリーを追加",
"addShortMemoryPlaceholder": "短期メモリーの内容を入力、現在の会話のみ有効",
"noShortMemories": "短期メモリーなし",
"noCurrentTopic": "まず会話トピックを選択してください",
"confirmDelete": "削除の確認",
"confirmDeleteContent": "この短期メモリーを削除しますか?",
"delete": "削除"
}
},
"translate": {

View File

@ -1396,6 +1396,7 @@
"toggleActive": "[to be translated]:切换激活状态",
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
"shortMemory": "[to be translated]:短期记忆",
"longMemory": "[to be translated]:长期记忆",
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
"addShortMemory": "[to be translated]:添加短期记忆",
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",

View File

@ -1029,7 +1029,8 @@
"description": "管理AI助手的长期记忆自动分析对话并提取重要信息",
"enableMemory": "启用记忆功能",
"enableAutoAnalyze": "启用自动分析",
"analyzeModel": "分析模型",
"analyzeModel": "长期记忆分析模型",
"shortMemoryAnalyzeModel": "短期记忆分析模型",
"selectModel": "选择模型",
"memoriesList": "记忆列表",
"memoryLists": "记忆角色",
@ -1051,6 +1052,66 @@
"analyzeNow": "立即分析",
"startingAnalysis": "开始分析...",
"cannotAnalyze": "无法分析,请检查设置",
"resetAnalyzingState": "重置分析状态",
"analyzeConversation": "分析对话",
"shortMemoryAnalysisSuccess": "分析成功",
"shortMemoryAnalysisSuccessContent": "已成功提取并添加重要信息到短期记忆",
"shortMemoryAnalysisNoNew": "无新信息",
"shortMemoryAnalysisNoNewContent": "未发现新的重要信息或所有信息已存在",
"shortMemoryAnalysisError": "分析失败",
"shortMemoryAnalysisErrorContent": "分析对话内容时出错",
"deduplication": {
"title": "记忆去重与合并",
"description": "分析记忆库中的相似记忆,提供智能合并建议。",
"selectList": "选择记忆列表",
"allLists": "所有列表",
"selectTopic": "选择话题",
"similarityThreshold": "相似度阈值",
"startAnalysis": "开始分析",
"help": "帮助",
"helpTitle": "记忆去重与合并帮助",
"helpContent1": "该功能会分析记忆库中的相似记忆,并提供合并建议。",
"helpContent2": "相似度阈值决定了两条记忆被认为相似的程度,值越高,要求越严格。",
"helpContent3": "应用结果后,相似的记忆将被合并为一条新记忆,原记忆将被删除。",
"analyzing": "分析中...",
"noSimilarMemories": "未发现相似记忆",
"similarGroups": "相似记忆组",
"group": "组",
"items": "项",
"originalMemories": "原始记忆",
"mergedResult": "合并结果",
"other": "其他",
"applyResults": "应用结果",
"confirmApply": "确认应用去重结果",
"confirmApplyContent": "应用去重结果将合并相似记忆并删除原记忆,此操作不可撤销。确定要继续吗?",
"applySuccess": "应用成功",
"applySuccessContent": "记忆去重与合并已成功应用"
},
"shortMemoryDeduplication": {
"title": "短期记忆去重与合并",
"description": "分析短期记忆中的相似记忆,提供智能合并建议。",
"selectTopic": "选择话题",
"similarityThreshold": "相似度阈值",
"startAnalysis": "开始分析",
"help": "帮助",
"helpTitle": "短期记忆去重与合并帮助",
"helpContent1": "该功能会分析短期记忆中的相似记忆,并提供合并建议。",
"helpContent2": "相似度阈值决定了两条记忆被认为相似的程度,值越高,要求越严格。",
"helpContent3": "应用结果后,相似的记忆将被合并为一条新记忆,原记忆将被删除。",
"analyzing": "分析中...",
"noSimilarMemories": "未发现相似记忆",
"similarGroups": "相似记忆组",
"group": "组",
"items": "项",
"originalMemories": "原始记忆",
"mergedResult": "合并结果",
"other": "其他",
"applyResults": "应用结果",
"confirmApply": "确认应用去重结果",
"confirmApplyContent": "应用去重结果将合并相似记忆并删除原记忆,此操作不可撤销。确定要继续吗?",
"applySuccess": "应用成功",
"applySuccessContent": "短期记忆去重与合并已成功应用"
},
"selectTopic": "选择话题",
"selectTopicPlaceholder": "选择要分析的话题",
"filterByCategory": "按分类筛选",
@ -1068,6 +1129,12 @@
"toggleActive": "切换激活状态",
"clearConfirmContentList": "确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
"shortMemory": "短期记忆",
"loading": "加载中...",
"longMemory": "长期记忆",
"shortMemorySettings": "短期记忆设置",
"shortMemoryDescription": "管理与当前对话相关的短期记忆",
"longMemorySettings": "长期记忆设置",
"longMemoryDescription": "管理跨对话的长期记忆",
"toggleShortMemoryActive": "切换短期记忆功能",
"addShortMemory": "添加短期记忆",
"addShortMemoryPlaceholder": "输入短期记忆内容,只在当前对话中有效",
@ -1075,7 +1142,9 @@
"noCurrentTopic": "请先选择一个对话话题",
"confirmDelete": "确认删除",
"confirmDeleteContent": "确定要删除这条短期记忆吗?",
"delete": "删除"
"delete": "删除",
"allTopics": "所有话题",
"noTopics": "没有话题"
},
"mcp": {
"actions": "操作",

View File

@ -1353,57 +1353,58 @@
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
},
"memory": {
"title": "[to be translated]:记忆功能",
"description": "[to be translated]:管理AI助手的长期记忆自动分析对话并提取重要信息",
"enableMemory": "[to be translated]:启用记忆功能",
"enableAutoAnalyze": "[to be translated]:启用自动分析",
"analyzeModel": "[to be translated]:分析模型",
"selectModel": "[to be translated]:选择模型",
"memoriesList": "[to be translated]:记忆列表",
"memoryLists": "[to be translated]:记忆角色",
"addMemory": "[to be translated]:添加记忆",
"editMemory": "[to be translated]:编辑记忆",
"clearAll": "[to be translated]:清空全部",
"noMemories": "[to be translated]:暂无记忆",
"memoryPlaceholder": "[to be translated]:输入要记住的内容",
"addSuccess": "[to be translated]:记忆添加成功",
"editSuccess": "[to be translated]:记忆编辑成功",
"deleteSuccess": "[to be translated]:记忆删除成功",
"clearSuccess": "[to be translated]:记忆清空成功",
"clearConfirmTitle": "[to be translated]:确认清空",
"clearConfirmContent": "[to be translated]:确定要清空所有记忆吗?此操作无法撤销。",
"listView": "[to be translated]:列表视图",
"mindmapView": "[to be translated]:思维导图",
"centerNodeLabel": "[to be translated]:用户记忆",
"manualAnalyze": "[to be translated]:手动分析",
"analyzeNow": "[to be translated]:立即分析",
"startingAnalysis": "[to be translated]:开始分析...",
"cannotAnalyze": "[to be translated]:无法分析,请检查设置",
"selectTopic": "[to be translated]:选择话题",
"selectTopicPlaceholder": "[to be translated]:选择要分析的话题",
"filterByCategory": "[to be translated]:按分类筛选",
"allCategories": "[to be translated]:全部",
"uncategorized": "[to be translated]:未分类",
"addList": "[to be translated]:添加记忆列表",
"editList": "[to be translated]:编辑记忆列表",
"listName": "[to be translated]:列表名称",
"listNamePlaceholder": "[to be translated]:输入列表名称",
"listDescription": "[to be translated]:列表描述",
"listDescriptionPlaceholder": "[to be translated]:输入列表描述(可选)",
"noLists": "[to be translated]:暂无记忆列表",
"confirmDeleteList": "[to be translated]:确认删除列表",
"confirmDeleteListContent": "[to be translated]:确定要删除 {{name}} 列表吗?此操作将同时删除列表中的所有记忆,且不可恢复。",
"toggleActive": "[to be translated]:切换激活状态",
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
"shortMemory": "[to be translated]:短期记忆",
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
"addShortMemory": "[to be translated]:添加短期记忆",
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
"noShortMemories": "[to be translated]:暂无短期记忆",
"noCurrentTopic": "[to be translated]:请先选择一个对话话题",
"confirmDelete": "[to be translated]:确认删除",
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
"delete": "[to be translated]:删除"
"title": "記憶功能",
"description": "管理AI助手的長期記憶自動分析對話並提取重要信息",
"enableMemory": "啟用記憶功能",
"enableAutoAnalyze": "啟用自動分析",
"analyzeModel": "分析模型",
"selectModel": "選擇模型",
"memoriesList": "記憶列表",
"memoryLists": "記憶角色",
"addMemory": "添加記憶",
"editMemory": "編輯記憶",
"clearAll": "清空全部",
"noMemories": "暫無記憶",
"memoryPlaceholder": "輸入要記住的內容",
"addSuccess": "記憶添加成功",
"editSuccess": "記憶編輯成功",
"deleteSuccess": "記憶刪除成功",
"clearSuccess": "記憶清空成功",
"clearConfirmTitle": "確認清空",
"clearConfirmContent": "確定要清空所有記憶嗎?此操作無法撤銷。",
"listView": "列表視圖",
"mindmapView": "思維導圖",
"centerNodeLabel": "用戶記憶",
"manualAnalyze": "手動分析",
"analyzeNow": "立即分析",
"startingAnalysis": "開始分析...",
"cannotAnalyze": "無法分析,請檢查設置",
"selectTopic": "選擇話題",
"selectTopicPlaceholder": "選擇要分析的話題",
"filterByCategory": "按分類篩選",
"allCategories": "全部",
"uncategorized": "未分類",
"addList": "添加記憶列表",
"editList": "編輯記憶列表",
"listName": "列表名稱",
"listNamePlaceholder": "輸入列表名稱",
"listDescription": "列表描述",
"listDescriptionPlaceholder": "輸入列表描述(可選)",
"noLists": "暫無記憶列表",
"confirmDeleteList": "確認刪除列表",
"confirmDeleteListContent": "確定要刪除 {{name}} 列表嗎?此操作將同時刪除列表中的所有記憶,且不可恢復。",
"toggleActive": "切換激活狀態",
"clearConfirmContentList": "確定要清空 {{name}} 中的所有記憶嗎?此操作不可恢復。",
"shortMemory": "短期記憶",
"longMemory": "長期記憶",
"toggleShortMemoryActive": "切換短期記憶功能",
"addShortMemory": "添加短期記憶",
"addShortMemoryPlaceholder": "輸入短期記憶內容,只在當前對話中有效",
"noShortMemories": "暫無短期記憶",
"noCurrentTopic": "請先選擇一個對話話題",
"confirmDelete": "確認刪除",
"confirmDeleteContent": "確定要刪除這條短期記憶嗎?",
"delete": "刪除"
}
},
"translate": {

View File

@ -1,8 +1,9 @@
import { FormOutlined, SearchOutlined } from '@ant-design/icons'
import { BookOutlined, FormOutlined, SearchOutlined } from '@ant-design/icons'
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover'
import SearchPopup from '@renderer/components/Popups/SearchPopup'
import ShortMemoryPopup from '@renderer/components/Popups/ShortMemoryPopup'
import { isMac } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { modelGenerating } from '@renderer/hooks/useRuntime'
@ -10,10 +11,11 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { analyzeAndAddShortMemories } from '@renderer/services/MemoryService'
import { useAppDispatch } from '@renderer/store'
import { setNarrowMode } from '@renderer/store/settings'
import { Assistant, Topic } from '@renderer/types'
import { Tooltip } from 'antd'
import { Button, Tooltip } from 'antd'
import { t } from 'i18next'
import { FC } from 'react'
import styled from 'styled-components'
@ -27,7 +29,7 @@ interface Props {
setActiveTopic: (topic: Topic) => void
}
const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
const HeaderNavbar: FC<Props> = ({ activeAssistant, activeTopic }) => {
const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants()
const { topicPosition, sidebarIcons, narrowMode } = useSettings()
@ -55,6 +57,28 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
dispatch(setNarrowMode(!narrowMode))
}
const handleShowShortMemory = () => {
if (activeTopic && activeTopic.id) {
ShortMemoryPopup.show({ topicId: activeTopic.id })
}
}
const handleAnalyzeShortMemory = async () => {
if (activeTopic && activeTopic.id) {
try {
const result = await analyzeAndAddShortMemories(activeTopic.id)
if (result) {
window.message.success(t('settings.memory.shortMemoryAnalysisSuccess') || '分析成功')
} else {
window.message.info(t('settings.memory.shortMemoryAnalysisNoNew') || '无新信息')
}
} catch (error) {
console.error('Failed to analyze conversation for short memory:', error)
window.message.error(t('settings.memory.shortMemoryAnalysisError') || '分析失败')
}
}
}
return (
<Navbar className="home-navbar">
{showAssistants && (
@ -86,6 +110,14 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
</HStack>
<HStack alignItems="center" gap={8}>
<UpdateAppButton />
<Tooltip title={t('settings.memory.shortMemory')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={handleShowShortMemory}>
<BookOutlined />
</NarrowIcon>
</Tooltip>
<AnalyzeButton onClick={handleAnalyzeShortMemory}>
{t('settings.memory.analyzeConversation') || '分析对话'}
</AnalyzeButton>
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={() => SearchPopup.show()}>
<SearchOutlined />
@ -156,4 +188,17 @@ const NarrowIcon = styled(NavbarIcon)`
}
`
const AnalyzeButton = styled(Button)`
font-size: 12px;
height: 28px;
padding: 0 10px;
border-radius: 4px;
margin-right: 8px;
-webkit-app-region: none;
@media (max-width: 1000px) {
display: none;
}
`
export default HeaderNavbar

View File

@ -1,11 +1,11 @@
import { Handle, Position } from '@xyflow/react';
import { Card, Typography } from 'antd';
import styled from 'styled-components';
import { Handle, Position } from '@xyflow/react'
import { Card, Typography } from 'antd'
import styled from 'styled-components'
interface CenterNodeProps {
data: {
label: string;
};
label: string
}
}
const CenterNode: React.FC<CenterNodeProps> = ({ data }) => {
@ -19,12 +19,12 @@ const CenterNode: React.FC<CenterNodeProps> = ({ data }) => {
<Handle type="source" position={Position.Left} id="l" />
<Handle type="source" position={Position.Top} id="t" />
</NodeContainer>
);
};
)
}
const NodeContainer = styled.div`
width: 150px;
text-align: center;
`;
`
export default CenterNode;
export default CenterNode

View File

@ -0,0 +1,299 @@
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { TopicManager } from '@renderer/hooks/useTopic'
import { addShortMemoryItem } from '@renderer/services/MemoryService'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { deleteShortMemory, setShortMemoryActive, ShortMemory } from '@renderer/store/memory' // Import ShortMemory from here
import { Topic } from '@renderer/types' // Remove ShortMemory import from here
import { Button, Collapse, Empty, Input, List, Modal, Switch, Tooltip, Typography } from 'antd'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
const { Title } = Typography
const { confirm } = Modal
// const { Panel } = Collapse // Panel is no longer used
const HeaderContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
`
const InputContainer = styled.div`
margin-bottom: 16px;
`
const LoadingContainer = styled.div`
text-align: center;
padding: 20px 0;
`
const AddButton = styled(Button)`
margin-top: 8px;
`
interface TopicWithMemories {
topic: Topic
memories: ShortMemory[]
}
const CollapsibleShortMemoryManager = () => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
// 获取短记忆状态
const shortMemoryActive = useAppSelector((state) => state.memory?.shortMemoryActive || false)
const shortMemories = useAppSelector((state) => state.memory?.shortMemories || [])
// 获取当前话题ID
const currentTopicId = useAppSelector((state) => state.messages?.currentTopic?.id)
// 添加短记忆的状态
const [newMemoryContent, setNewMemoryContent] = useState('')
// 话题列表和话题记忆映射
const [topicsWithMemories, setTopicsWithMemories] = useState<TopicWithMemories[]>([])
const [loading, setLoading] = useState(true)
// 加载所有话题和对应的短期记忆
useEffect(() => {
const loadTopicsWithMemories = async () => {
try {
setLoading(true)
// 从数据库获取所有话题
const allTopics = await TopicManager.getAllTopics()
// 获取所有助手及其话题,确保我们使用与左侧列表相同的话题名称
const assistants = store.getState().assistants?.assistants || []
const allAssistantTopics = assistants.flatMap((assistant) => assistant.topics || [])
if (allTopics && allTopics.length > 0) {
// 创建话题和记忆的映射
const topicsMemories: TopicWithMemories[] = []
for (const dbTopic of allTopics) {
// 获取该话题的短期记忆
const topicMemories = shortMemories.filter((memory) => memory.topicId === dbTopic.id)
// 只添加有短期记忆的话题
if (topicMemories.length > 0) {
// 首先尝试从助手的话题列表中找到完整的话题信息
let topicInfo = allAssistantTopics.find((topic) => topic.id === dbTopic.id)
// 如果在助手话题中找不到,则尝试从数据库获取
if (!topicInfo) {
try {
const fullTopic = await TopicManager.getTopic(dbTopic.id)
if (fullTopic) {
// 数据库中的话题可能没有name属性所以需要手动构造
// 使用默认的话题名称格式
const topicName = `话题 ${dbTopic.id.substring(0, 8)}`
topicInfo = {
id: dbTopic.id,
assistantId: '',
name: topicName,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
messages: []
}
}
} catch (error) {
console.error(`Failed to get topic name for ${dbTopic.id}:`, error)
}
}
// 如果还是找不到,使用默认名称
if (!topicInfo) {
topicInfo = {
id: dbTopic.id,
assistantId: '',
name: `话题 ${dbTopic.id.substring(0, 8)}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
messages: []
}
}
topicsMemories.push({
topic: topicInfo,
memories: topicMemories
})
}
}
// 按更新时间排序,最新的在前
const sortedTopicsMemories = topicsMemories.sort((a, b) => {
// 使用最新记忆的时间进行排序
const aLatestMemory = a.memories.sort(
(m1, m2) => new Date(m2.createdAt).getTime() - new Date(m1.createdAt).getTime()
)[0]
const bLatestMemory = b.memories.sort(
(m1, m2) => new Date(m2.createdAt).getTime() - new Date(m1.createdAt).getTime()
)[0]
return new Date(bLatestMemory.createdAt).getTime() - new Date(aLatestMemory.createdAt).getTime()
})
setTopicsWithMemories(sortedTopicsMemories)
}
} catch (error) {
console.error('Failed to load topics with memories:', error)
} finally {
setLoading(false)
}
}
if (shortMemories.length > 0) {
loadTopicsWithMemories()
} else {
setTopicsWithMemories([])
setLoading(false)
}
}, [shortMemories])
// 切换短记忆功能激活状态
const handleToggleActive = (checked: boolean) => {
dispatch(setShortMemoryActive(checked))
}
// 添加新的短记忆
const handleAddMemory = () => {
if (newMemoryContent.trim() && currentTopicId) {
addShortMemoryItem(newMemoryContent.trim(), currentTopicId)
setNewMemoryContent('') // 清空输入框
}
}
// 删除短记忆
const handleDeleteMemory = (id: string) => {
confirm({
title: t('settings.memory.confirmDelete'),
icon: <ExclamationCircleOutlined />,
content: t('settings.memory.confirmDeleteContent'),
onOk() {
dispatch(deleteShortMemory(id))
}
})
}
return (
<div className="short-memory-manager">
<HeaderContainer>
<Title level={4}>{t('settings.memory.shortMemory')}</Title>
<Tooltip title={t('settings.memory.toggleShortMemoryActive')}>
<Switch checked={shortMemoryActive} onChange={handleToggleActive} />
</Tooltip>
</HeaderContainer>
<InputContainer>
<Input.TextArea
value={newMemoryContent}
onChange={(e) => setNewMemoryContent(e.target.value)}
placeholder={t('settings.memory.addShortMemoryPlaceholder')}
autoSize={{ minRows: 2, maxRows: 4 }}
disabled={!shortMemoryActive || !currentTopicId}
/>
<AddButton
type="primary"
onClick={handleAddMemory}
disabled={!shortMemoryActive || !newMemoryContent.trim() || !currentTopicId}>
{t('settings.memory.addShortMemory')}
</AddButton>
</InputContainer>
<div className="short-memories-list">
{loading ? (
<LoadingContainer>{t('settings.memory.loading') || '加载中...'}</LoadingContainer>
) : topicsWithMemories.length > 0 ? (
<StyledCollapse
defaultActiveKey={[currentTopicId || '']}
items={topicsWithMemories.map(({ topic, memories }) => ({
key: topic.id,
label: (
<CollapseHeader>
<span>{topic.name}</span>
<MemoryCount>{memories.length}</MemoryCount>
</CollapseHeader>
),
children: (
<List
itemLayout="horizontal"
dataSource={memories}
renderItem={(memory) => (
<List.Item
actions={[
<Tooltip title={t('settings.memory.delete')} key="delete">
<Button
icon={<DeleteOutlined />}
onClick={() => handleDeleteMemory(memory.id)}
type="text"
danger
/>
</Tooltip>
]}>
<List.Item.Meta
title={<MemoryContent>{memory.content}</MemoryContent>}
description={new Date(memory.createdAt).toLocaleString()}
/>
</List.Item>
)}
/>
)
}))}
/>
) : (
<Empty
description={!currentTopicId ? t('settings.memory.noCurrentTopic') : t('settings.memory.noShortMemories')}
/>
)}
</div>
</div>
)
}
const StyledCollapse = styled(Collapse)`
background-color: transparent;
border: none;
.ant-collapse-item {
border: 1px solid var(--color-border);
border-radius: 8px !important;
margin-bottom: 8px;
overflow: hidden;
}
.ant-collapse-header {
background-color: var(--color-background-soft);
padding: 8px 16px !important;
}
.ant-collapse-content {
border-top: 1px solid var(--color-border);
}
`
const CollapseHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`
const MemoryCount = styled.span`
background-color: var(--color-primary);
color: white;
border-radius: 10px;
padding: 0 8px;
font-size: 12px;
min-width: 20px;
text-align: center;
`
const MemoryContent = styled.div`
word-break: break-word;
`
export default CollapsibleShortMemoryManager

View File

@ -0,0 +1,440 @@
import {
CheckCircleOutlined,
DownOutlined,
MergeCellsOutlined,
QuestionCircleOutlined,
RightOutlined
} from '@ant-design/icons'
import { TopicManager } from '@renderer/hooks/useTopic'
import {
applyDeduplicationResult,
deduplicateAndMergeMemories,
DeduplicationResult
} from '@renderer/services/MemoryDeduplicationService'
import { useAppSelector } from '@renderer/store'
import store from '@renderer/store'
import { Topic } from '@renderer/types'
import { Button, Card, Collapse, Empty, List, Modal, Slider, Space, Spin, Tag, Typography } from 'antd'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
// 使用items属性不再需要Panel组件
const { Title, Text, Paragraph } = Typography
interface MemoryDeduplicationPanelProps {
title?: string
description?: string
translationPrefix?: string
applyResults?: (result: DeduplicationResult) => void
isShortMemory?: boolean
}
const MemoryDeduplicationPanel: React.FC<MemoryDeduplicationPanelProps> = ({
title,
description,
translationPrefix = 'settings.memory.deduplication',
applyResults,
isShortMemory = false
}) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [deduplicationResult, setDeduplicationResult] = useState<DeduplicationResult | null>(null)
const [threshold, setThreshold] = useState(0.75) // 降低默认阈值以捕获更多相似记忆
const [selectedListId, setSelectedListId] = useState<string | undefined>(undefined)
const [selectedTopicId, setSelectedTopicId] = useState<string | undefined>(undefined)
const [topicsList, setTopicsList] = useState<Topic[]>([])
const [loadingTopics, setLoadingTopics] = useState(false)
// 获取记忆列表
const memoryLists = useAppSelector((state) => state.memory?.memoryLists || [])
const memories = useAppSelector((state) =>
isShortMemory ? state.memory?.shortMemories || [] : state.memory?.memories || []
)
// 加载有短期记忆的话题
useEffect(() => {
const loadTopics = async () => {
try {
setLoadingTopics(true)
// 获取短期记忆
const shortMemories = store.getState().memory?.shortMemories || []
// 获取所有有短期记忆的话题ID
const topicIds = Array.from(new Set(shortMemories.map((memory) => memory.topicId)))
if (topicIds.length > 0) {
// 获取所有助手及其话题,确保我们使用与左侧列表相同的话题名称
const assistants = store.getState().assistants?.assistants || []
const allAssistantTopics = assistants.flatMap((assistant) => assistant.topics || [])
// 创建完整的话题列表
const fullTopics: Topic[] = []
for (const topicId of topicIds) {
// 首先尝试从助手的话题列表中找到完整的话题信息
let topicInfo = allAssistantTopics.find((topic) => topic.id === topicId)
// 如果在助手话题中找不到,则尝试从数据库获取
if (!topicInfo) {
try {
const dbTopic = await TopicManager.getTopic(topicId)
if (dbTopic) {
topicInfo = {
id: dbTopic.id,
assistantId: '',
name: `话题 ${dbTopic.id.substring(0, 8)}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
messages: []
}
}
} catch (error) {
console.error(`Failed to get topic name for ${topicId}:`, error)
}
}
// 如果找到了话题信息,添加到列表中
if (topicInfo) {
fullTopics.push(topicInfo)
}
}
// 按更新时间排序,最新的在前
const sortedTopics = fullTopics.sort((a, b) => {
return new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime()
})
setTopicsList(sortedTopics)
}
} catch (error) {
console.error('Failed to load topics:', error)
} finally {
setLoadingTopics(false)
}
}
loadTopics()
}, [])
// 开始去重分析
const handleDeduplication = async () => {
setIsLoading(true)
try {
if (isShortMemory) {
// 短期记忆去重
const result = await deduplicateAndMergeMemories(undefined, true, selectedTopicId)
setDeduplicationResult(result)
} else {
// 长期记忆去重
const result = await deduplicateAndMergeMemories(selectedListId, false)
setDeduplicationResult(result)
}
} finally {
setIsLoading(false)
}
}
// 应用去重结果
const handleApplyResult = () => {
if (!deduplicationResult) return
Modal.confirm({
title: t(`${translationPrefix}.confirmApply`),
content: t(`${translationPrefix}.confirmApplyContent`),
onOk: () => {
if (applyResults) {
// 使用自定义的应用函数
applyResults(deduplicationResult)
} else {
// 使用默认的应用函数
applyDeduplicationResult(deduplicationResult, true, isShortMemory)
}
setDeduplicationResult(null)
Modal.success({
title: t(`${translationPrefix}.applySuccess`),
content: t(`${translationPrefix}.applySuccessContent`)
})
}
})
}
// 获取记忆内容 - 这个函数在renderItem中使用确保没有删除错误
const getMemoryContent = (index: string) => {
const memoryIndex = parseInt(index) - 1
if (memoryIndex >= 0 && memoryIndex < memories.length) {
const memory = memories[memoryIndex]
return {
content: memory.content,
category: 'category' in memory ? memory.category || '其他' : '其他'
}
}
return { content: '', category: '' }
}
// 函数 getMemories 在第38行报错未使用不是 getMemoryContent
// 将删除报错的 getMemories 函数 (实际检查代码发现没有 getMemories 函数,可能之前已删除或误报,先跳过此文件)
// 渲染结果
const renderResult = () => {
if (!deduplicationResult) return null
if (deduplicationResult.similarGroups.length === 0) {
return (
<Empty
description={t('settings.memory.deduplication.noSimilarMemories')}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
)
}
return (
<div>
<Title level={5}>{t('settings.memory.deduplication.similarGroups')}</Title>
<Collapse
items={deduplicationResult.similarGroups.map((group) => ({
key: group.groupId,
label: (
<Space>
<Text strong>
{t('settings.memory.deduplication.group')} {group.groupId}
</Text>
<Text type="secondary">
({group.memoryIds.length} {t('settings.memory.deduplication.items')})
</Text>
{group.category && <Tag color="blue">{group.category}</Tag>}
</Space>
),
children: (
<>
<Card
title={t('settings.memory.deduplication.originalMemories')}
size="small"
style={{ marginBottom: 16 }}>
<List
size="small"
dataSource={group.memoryIds}
renderItem={(id) => {
const memory = getMemoryContent(id)
return (
<List.Item>
<List.Item.Meta
title={<Text code>{id}</Text>}
description={
<>
<Tag color="cyan">{memory.category}</Tag>
<Text>{memory.content}</Text>
</>
}
/>
</List.Item>
)
}}
/>
</Card>
<Card title={t('settings.memory.deduplication.mergedResult')} size="small">
<Paragraph>
<Tag color="green">{group.category || t('settings.memory.deduplication.other')}</Tag>
<Text strong>{group.mergedContent}</Text>
</Paragraph>
</Card>
</>
)
}))}
/>
<div style={{ marginTop: 16, textAlign: 'center' }}>
<Button type="primary" icon={<CheckCircleOutlined />} onClick={handleApplyResult}>
{t('settings.memory.deduplication.applyResults')}
</Button>
</div>
</div>
)
}
// 切换折叠状态
const toggleExpand = () => {
setIsExpanded(!isExpanded)
}
return (
<StyledCard>
<CollapsibleHeader onClick={toggleExpand}>
<HeaderContent>
<Title level={5}>{title || t(`${translationPrefix}.title`)}</Title>
{isExpanded ? <DownOutlined /> : <RightOutlined />}
</HeaderContent>
</CollapsibleHeader>
{isExpanded && (
<CollapsibleContent>
<Paragraph>{description || t(`${translationPrefix}.description`)}</Paragraph>
<ControlsContainer>
{!isShortMemory ? (
<div>
<Text>{t(`${translationPrefix}.selectList`)}</Text>
<Select
value={selectedListId || 'all'}
onChange={(e) => setSelectedListId(e.target.value === 'all' ? undefined : e.target.value)}>
<option value="all">{t(`${translationPrefix}.allLists`)}</option>
{memoryLists.map((list) => (
<option key={list.id} value={list.id}>
{list.name}
</option>
))}
</Select>
</div>
) : (
<div>
<Text>{t(`${translationPrefix}.selectTopic`) || '选择话题'}</Text>
<Select
value={selectedTopicId || 'all'}
onChange={(e) => setSelectedTopicId(e.target.value === 'all' ? undefined : e.target.value)}>
<option value="all">{t('settings.memory.allTopics') || '所有话题'}</option>
{loadingTopics ? (
<option disabled>{t('settings.memory.loading') || '加载中...'}</option>
) : topicsList.length > 0 ? (
topicsList.map((topic) => (
<option key={topic.id} value={topic.id}>
{topic.name || `话题 ${topic.id.substring(0, 8)}`}
</option>
))
) : (
<option disabled>{t('settings.memory.noTopics') || '没有话题'}</option>
)}
</Select>
</div>
)}
<div>
<Text>
{t(`${translationPrefix}.similarityThreshold`)}: {threshold}
</Text>
<Slider
min={0.5}
max={0.95}
step={0.05}
value={threshold}
onChange={setThreshold}
style={{ width: 200 }}
/>
</div>
</ControlsContainer>
<ButtonContainer>
<Button
type="primary"
icon={<MergeCellsOutlined />}
onClick={handleDeduplication}
loading={isLoading}
disabled={memories.length < 2}>
{t(`${translationPrefix}.startAnalysis`)}
</Button>
<Button
icon={<QuestionCircleOutlined />}
onClick={() => {
Modal.info({
title: t(`${translationPrefix}.helpTitle`),
content: (
<div>
<Paragraph>{t(`${translationPrefix}.helpContent1`)}</Paragraph>
<Paragraph>{t(`${translationPrefix}.helpContent2`)}</Paragraph>
<Paragraph>{t(`${translationPrefix}.helpContent3`)}</Paragraph>
</div>
)
})
}}>
{t(`${translationPrefix}.help`)}
</Button>
</ButtonContainer>
{isLoading ? (
<LoadingContainer>
<Spin size="large" />
<Text>{t(`${translationPrefix}.analyzing`)}</Text>
</LoadingContainer>
) : (
<ResultContainer>{renderResult()}</ResultContainer>
)}
</CollapsibleContent>
)}
</StyledCard>
)
}
const StyledCard = styled(Card)`
margin-bottom: 24px;
border-radius: 8px;
overflow: hidden;
`
const CollapsibleHeader = styled.div`
cursor: pointer;
padding: 12px 16px;
background-color: var(--color-background-secondary, #f5f5f5);
border-bottom: 1px solid var(--color-border, #e8e8e8);
transition: background-color 0.3s;
&:hover {
background-color: var(--color-background-hover, #e6f7ff);
}
`
const HeaderContent = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
const CollapsibleContent = styled.div`
padding: 16px;
`
const ControlsContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: 24px;
margin-bottom: 16px;
`
const ButtonContainer = styled.div`
display: flex;
gap: 8px;
margin-bottom: 24px;
`
const LoadingContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 0;
gap: 16px;
`
const ResultContainer = styled.div`
margin-top: 16px;
`
// ApplyButtonContainer seems unused, removing it.
// const ApplyButtonContainer = styled.div`
// margin-top: 16px;
// text-align: center;
// `
const Select = styled.select`
display: block;
width: 100%;
margin-top: 8px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 4px;
background-color: var(--color-background);
color: var(--color-text);
`
export default MemoryDeduplicationPanel

View File

@ -1,19 +1,19 @@
import { DeleteOutlined, EditOutlined, TagOutlined } from '@ant-design/icons';
import { Memory } from '@renderer/store/memory';
import { Button, Card, Tag, Tooltip, Typography } from 'antd';
import { Handle, Position } from '@xyflow/react';
import styled from 'styled-components';
import { DeleteOutlined, EditOutlined, TagOutlined } from '@ant-design/icons'
import { Memory } from '@renderer/store/memory'
import { Handle, Position } from '@xyflow/react'
import { Button, Card, Tag, Tooltip, Typography } from 'antd'
import styled from 'styled-components'
interface MemoryNodeProps {
data: {
memory: Memory;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
};
memory: Memory
onEdit: (id: string) => void
onDelete: (id: string) => void
}
}
const MemoryNode: React.FC<MemoryNodeProps> = ({ data }) => {
const { memory, onEdit, onDelete } = data;
const { memory, onEdit, onDelete } = data
return (
<NodeContainer>
@ -35,43 +35,31 @@ const MemoryNode: React.FC<MemoryNodeProps> = ({ data }) => {
extra={
<div>
<Tooltip title="编辑">
<Button
icon={<EditOutlined />}
type="text"
size="small"
onClick={() => onEdit(memory.id)}
/>
<Button icon={<EditOutlined />} type="text" size="small" onClick={() => onEdit(memory.id)} />
</Tooltip>
<Tooltip title="删除">
<Button
icon={<DeleteOutlined />}
type="text"
danger
size="small"
onClick={() => onDelete(memory.id)}
/>
<Button icon={<DeleteOutlined />} type="text" danger size="small" onClick={() => onDelete(memory.id)} />
</Tooltip>
</div>
}
>
}>
<MemoryMeta>
<span>{new Date(memory.createdAt).toLocaleString()}</span>
{memory.source && <span>{memory.source}</span>}
</MemoryMeta>
</Card>
</NodeContainer>
);
};
)
}
const NodeContainer = styled.div`
width: 220px;
`;
`
const MemoryMeta = styled.div`
display: flex;
flex-direction: column;
font-size: 12px;
color: var(--color-text-secondary);
`;
`
export default MemoryNode;
export default MemoryNode

File diff suppressed because it is too large Load Diff

View File

@ -164,6 +164,8 @@ const ContentContainer = styled.div`
display: flex;
flex: 1;
flex-direction: row;
height: calc(100vh - var(--navbar-height)); /* 设置高度为视口高度减去导航栏高度 */
overflow: hidden; /* 防止内容溢出 */
`
const SettingMenus = styled.ul`
@ -173,6 +175,26 @@ const SettingMenus = styled.ul`
border-right: 0.5px solid var(--color-border);
padding: 10px;
user-select: none;
overflow-y: auto; /* 允许菜单滚动 */
/* 添加滚动条样式 */
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--color-primary);
}
&::-webkit-scrollbar-track {
background: transparent;
}
`
const MenuItemLink = styled(Link)`
@ -217,6 +239,26 @@ const SettingContent = styled.div`
height: 100%;
flex: 1;
border-right: 0.5px solid var(--color-border);
overflow-y: auto; /* 添加滚动属性,允许内容滚动 */
/* 添加滚动条样式 */
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--color-primary);
}
&::-webkit-scrollbar-track {
background: transparent;
}
`
export default SettingsPage

View File

@ -12,18 +12,49 @@ export const SettingContainer = styled.div<{ theme?: ThemeMode }>`
padding: 20px;
padding-top: 15px;
padding-bottom: 75px;
overflow-y: scroll;
overflow-y: auto; /* 改为auto只在需要时显示滚动条 */
font-family: Ubuntu;
background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')};
/* 添加滚动指示器 */
&::after {
content: '';
position: fixed;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--color-primary);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>');
background-repeat: no-repeat;
background-position: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
&.scrollable::after {
opacity: 0.7;
}
&::-webkit-scrollbar {
width: 8px;
height: 8px;
width: 10px;
height: 10px;
}
&::-webkit-scrollbar-thumb {
background: var(--color-border);
border-radius: 4px;
border-radius: 5px;
border: 2px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--color-primary);
border: 2px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-track {

View File

@ -37,7 +37,15 @@ export default abstract class BaseProvider {
abstract summaries(messages: Message[], assistant: Assistant): Promise<string>
abstract summaryForSearch(messages: Message[], assistant: Assistant): Promise<string | null>
abstract suggestions(messages: Message[], assistant: Assistant): Promise<Suggestion[]>
abstract generateText({ prompt, content, modelId }: { prompt: string; content: string; modelId?: string }): Promise<string>
abstract generateText({
prompt,
content,
modelId
}: {
prompt: string
content: string
modelId?: string
}): Promise<string>
abstract check(model: Model): Promise<{ valid: boolean; error: Error | null }>
abstract models(): Promise<OpenAI.Models.Model[]>
abstract generateImage(params: GenerateImageParams): Promise<string[]>

View File

@ -88,7 +88,15 @@ export default class AiProvider {
return this.sdk.suggestions(messages, assistant)
}
public async generateText({ prompt, content, modelId }: { prompt: string; content: string; modelId?: string }): Promise<string> {
public async generateText({
prompt,
content,
modelId
}: {
prompt: string
content: string
modelId?: string
}): Promise<string> {
return this.sdk.generateText({ prompt, content, modelId })
}

View File

@ -0,0 +1,279 @@
// 记忆去重与合并服务
import { fetchGenerate } from '@renderer/services/ApiService'
import store from '@renderer/store'
import { addMemory, addShortMemory, deleteMemory, deleteShortMemory } from '@renderer/store/memory'
// 记忆去重与合并的结果接口
export interface DeduplicationResult {
similarGroups: {
groupId: string
memoryIds: string[]
mergedContent: string
category?: string
}[]
independentMemories: string[]
rawResponse: string
}
/**
*
* @param listId ID
* @param isShortMemory
* @param topicId ID
* @returns
*/
export const deduplicateAndMergeMemories = async (
listId?: string,
isShortMemory: boolean = false,
topicId?: string
): Promise<DeduplicationResult | null> => {
// 获取需要处理的记忆
const state = store.getState()
let targetMemories: any[] = []
if (isShortMemory) {
// 处理短期记忆
const shortMemories = state.memory?.shortMemories || []
targetMemories = topicId ? shortMemories.filter((memory) => memory.topicId === topicId) : shortMemories
} else {
// 处理长期记忆
const memories = state.memory?.memories || []
targetMemories = listId ? memories.filter((memory) => memory.listId === listId) : memories
}
if (targetMemories.length < 2) {
console.log('[Memory Deduplication] Not enough memories to deduplicate')
return null
}
const memoryType = isShortMemory ? 'short memories' : 'memories'
console.log(`[Memory Deduplication] Starting deduplication for ${targetMemories.length} ${memoryType}`)
// 构建去重提示词
const memoriesToCheck = targetMemories
.map((memory, index) => {
if (isShortMemory) {
return `${index + 1}. 短期记忆: ${memory.content}`
} else {
return `${index + 1}. ${memory.category || '其他'}: ${memory.content}`
}
})
.join('\n')
const prompt = `
1. 使
2.
3.
:
${memoriesToCheck}
- "用户喜欢简洁的界面设计""用户偏好简单直观的UI"
- "用户正在开发一个网站项目""用户在进行网站开发工作"
- "用户正在准备完成一个项目""用户正在进行一个项目的工作"
:
1. :
- 1: ["1,5,8"] - : "合并后的内容" - : "最合适的分类"
- 2: [] - : "合并后的内容" - : "最合适的分类"
...
2. : []
-
- 使
-
-
"未发现相似记忆"
`
try {
// 使用AI模型进行去重分析
const analyzeModel = state.memory?.analyzeModel
if (!analyzeModel) {
console.log('[Memory Deduplication] No analyze model set')
return null
}
console.log('[Memory Deduplication] Calling AI model for analysis...')
const result = await fetchGenerate({
prompt: prompt,
content: memoriesToCheck,
modelId: analyzeModel
})
if (!result) {
console.log('[Memory Deduplication] No result from AI analysis')
return null
}
console.log('[Memory Deduplication] Analysis result:', result)
// 解析结果
const similarGroups: DeduplicationResult['similarGroups'] = []
const independentMemories: string[] = []
// 检查是否没有发现相似记忆
if (result.includes('未发现相似记忆')) {
console.log('[Memory Deduplication] No similar memories found')
return {
similarGroups: [],
independentMemories: targetMemories.map((_, index) => String(index + 1)),
rawResponse: result
}
}
// 解析相似组
const similarGroupsMatch = result.match(/1\.\s*识别出的相似组:([\s\S]*?)(?=2\.\s*独立记忆项:|$)/i)
if (similarGroupsMatch && similarGroupsMatch[1]) {
const groupsText = similarGroupsMatch[1].trim()
const groupRegex = /-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*(?:-\s*分类:\s*"([^"]+)")?/g
let match
while ((match = groupRegex.exec(groupsText)) !== null) {
const groupId = match[1] || String(similarGroups.length + 1)
const memoryIndices = match[2].split(',').map((s) => s.trim())
const mergedContent = match[3].trim()
const category = match[4]?.trim()
similarGroups.push({
groupId,
memoryIds: memoryIndices,
mergedContent,
category
})
}
}
// 解析独立记忆项
const independentMatch = result.match(/2\.\s*独立记忆项:\s*\[([\d,\s]+)\]/i)
if (independentMatch && independentMatch[1]) {
independentMemories.push(...independentMatch[1].split(',').map((s) => s.trim()))
}
console.log('[Memory Deduplication] Parsed result:', { similarGroups, independentMemories })
return {
similarGroups,
independentMemories,
rawResponse: result
}
} catch (error) {
console.error('[Memory Deduplication] Error during deduplication:', error)
return null
}
}
/**
*
* @param result
* @param autoApply
* @param isShortMemory
*/
export const applyDeduplicationResult = (
result: DeduplicationResult,
autoApply: boolean = false,
isShortMemory: boolean = false
) => {
if (!result || !result.similarGroups || result.similarGroups.length === 0) {
console.log('[Memory Deduplication] No similar groups to apply')
return
}
const state = store.getState()
const memories = isShortMemory ? state.memory?.shortMemories || [] : state.memory?.memories || []
// 处理每个相似组
for (const group of result.similarGroups) {
// 获取组中的记忆
const memoryIndices = group.memoryIds.map((id) => parseInt(id) - 1)
const groupMemories = memoryIndices.map((index) => memories[index]).filter(Boolean)
if (groupMemories.length < 2) continue
// 获取第一个记忆的列表ID和其他属性
const firstMemory = groupMemories[0]
// 收集所有已分析过的消息ID
const allAnalyzedMessageIds = new Set<string>()
groupMemories.forEach((memory) => {
if (memory.analyzedMessageIds) {
memory.analyzedMessageIds.forEach((id) => allAnalyzedMessageIds.add(id))
}
})
// 找出最新的lastMessageId
let lastMessageId: string | undefined
groupMemories.forEach((memory) => {
if (memory.lastMessageId) {
if (!lastMessageId || new Date(memory.createdAt) > new Date(lastMessageId)) {
lastMessageId = memory.lastMessageId
}
}
})
// 找出所有关联的话题ID
const topicIds = new Set<string>()
groupMemories.forEach((memory) => {
if (memory.topicId) {
topicIds.add(memory.topicId)
}
})
// 如果自动应用,则添加合并后的记忆并删除原记忆
if (autoApply) {
if (isShortMemory) {
// 处理短期记忆
// 添加合并后的短期记忆
const topicId = topicIds.size === 1 ? Array.from(topicIds)[0] : undefined
if (topicId) {
store.dispatch(
addShortMemory({
content: group.mergedContent,
topicId: topicId,
analyzedMessageIds: Array.from(allAnalyzedMessageIds),
lastMessageId: lastMessageId
})
)
// 删除原短期记忆
for (const memory of groupMemories) {
store.dispatch(deleteShortMemory(memory.id))
}
}
} else {
// 处理长期记忆
// 安全地获取 listId 和 category因为它们只存在于 Memory 类型
const listId = 'listId' in firstMemory ? firstMemory.listId : undefined
const memoryCategory = 'category' in firstMemory ? firstMemory.category : undefined
// 添加合并后的记忆
store.dispatch(
addMemory({
content: group.mergedContent,
source: '自动合并',
category: group.category || memoryCategory || '其他', // 使用安全获取的 category
listId: listId, // 使用安全获取的 listId
analyzedMessageIds: Array.from(allAnalyzedMessageIds),
lastMessageId: lastMessageId,
topicId: topicIds.size === 1 ? Array.from(topicIds)[0] : undefined
})
)
// 删除原记忆
for (const memory of groupMemories) {
store.dispatch(deleteMemory(memory.id))
}
}
console.log(`[Memory Deduplication] Applied group ${group.groupId}: merged ${groupMemories.length} memories`)
}
}
}

View File

@ -11,11 +11,14 @@ import { useCallback, useEffect, useRef } from 'react' // Add useRef back
// 分析对话内容并提取重要信息
const analyzeConversation = async (
conversation: string,
modelId: string
modelId: string,
customPrompt?: string
): Promise<Array<{ content: string; category: string }>> => {
try {
// 构建分析提示词
const prompt = `
// 使用自定义提示词或默认提示词
const prompt =
customPrompt ||
`
@ -111,17 +114,18 @@ export const useMemoryService = () => {
// 增加可选的 topicId 参数,允许分析指定的话题
const analyzeAndAddMemories = useCallback(
async (topicId?: string) => {
// 如果没有提供话题ID则使用当前话题
// 在函数执行时获取最新状态
const currentState = store.getState() // Use imported store
const memoryState = currentState.memory || {}
const messagesState = currentState.messages || {}
// 检查isAnalyzing状态是否卡住超过5分钟)
// 检查isAnalyzing状态是否卡住超过1分钟)
if (memoryState.isAnalyzing && memoryState.lastAnalyzeTime) {
const now = Date.now()
const analyzeTime = memoryState.lastAnalyzeTime
if (now - analyzeTime > 5 * 60 * 1000) {
// 5分钟超时
if (now - analyzeTime > 1 * 60 * 1000) {
// 1分钟超时
console.log('[Memory Analysis] Analysis state stuck, resetting...')
dispatch(setAnalyzing(false))
}
@ -159,9 +163,42 @@ export const useMemoryService = () => {
}
}
const latestConversation = messages.map((msg) => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n')
if (!messages || messages.length === 0) {
console.log('[Memory Analysis] No messages to analyze.')
return
}
if (!latestConversation) {
// 获取现有的长期记忆
const existingMemories = store.getState().memory?.memories || []
const topicMemories = existingMemories.filter((memory) => memory.topicId === targetTopicId)
// 收集所有已分析过的消息ID
const analyzedMessageIds = new Set<string>()
topicMemories.forEach((memory) => {
if (memory.analyzedMessageIds) {
memory.analyzedMessageIds.forEach((id) => analyzedMessageIds.add(id))
}
})
// 找出未分析过的新消息
const newMessages = messages.filter((msg) => !analyzedMessageIds.has(msg.id))
if (newMessages.length === 0) {
console.log('[Memory Analysis] No new messages to analyze.')
return
}
console.log(`[Memory Analysis] Found ${newMessages.length} new messages to analyze.`)
// 构建新消息的对话内容
const newConversation = newMessages.map((msg) => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n')
// 获取已有的长期记忆内容
const existingMemoriesContent = topicMemories
.map((memory) => `${memory.category || '其他'}: ${memory.content}`)
.join('\n')
if (!newConversation) {
console.log('[Memory Analysis] No conversation content to analyze.')
return
}
@ -170,10 +207,37 @@ export const useMemoryService = () => {
dispatch(setAnalyzing(true))
console.log('[Memory Analysis] Starting analysis...')
console.log(`[Memory Analysis] Analyzing topic: ${targetTopicId}`)
console.log('[Memory Analysis] Conversation length:', latestConversation.length)
console.log('[Memory Analysis] Conversation length:', newConversation.length)
// 调用分析函数 (仍然是模拟的)
const memories = await analyzeConversation(latestConversation, memoryState.analyzeModel!)
// 构建长期记忆分析提示词,包含已有记忆和新对话
const prompt = `
类别: 信息内容
-
-
-
-
-
${
existingMemoriesContent
? `以下是已经提取的重要信息:
${existingMemoriesContent}
`
: '请确保每条信息都是简洁、准确的。如果没有找到重要信息,请返回空字符串。'
}
:
${newConversation}
`
// 调用分析函数,传递自定义提示词
const memories = await analyzeConversation(newConversation, memoryState.analyzeModel!, prompt)
console.log('[Memory Analysis] Analysis complete. Memories extracted:', memories)
// 添加提取的记忆
@ -193,12 +257,21 @@ export const useMemoryService = () => {
// 获取当前选中的列表ID
const currentListId = store.getState().memory?.currentListId || store.getState().memory?.memoryLists[0]?.id
// 收集新分析的消息ID
const newMessageIds = messages.map((msg) => msg.id)
// 获取最后一条消息的ID用于跟踪分析进度
const lastMessageId = messages[messages.length - 1]?.id
dispatch(
addMemory({
content: memory.content,
source: '自动分析',
category: memory.category,
listId: currentListId
listId: currentListId,
analyzedMessageIds: newMessageIds,
lastMessageId: lastMessageId,
topicId: targetTopicId
})
)
console.log(
@ -263,30 +336,205 @@ export const useMemoryService = () => {
return { analyzeAndAddMemories }
}
// 手动添加记忆
export const addMemoryItem = (content: string, listId?: string, category?: string) => {
// Use imported store directly
store.dispatch(
addMemory({
content,
source: '手动添加',
listId,
category
})
)
}
// 手动添加短记忆
export const addShortMemoryItem = (content: string, topicId: string) => {
export const addShortMemoryItem = (
content: string,
topicId: string,
analyzedMessageIds?: string[],
lastMessageId?: string
) => {
// Use imported store directly
store.dispatch(
addShortMemory({
content,
topicId
topicId,
analyzedMessageIds,
lastMessageId
})
)
}
// 分析对话内容并提取重要信息添加到短期记忆
export const analyzeAndAddShortMemories = async (topicId: string) => {
if (!topicId) {
console.log('[Short Memory Analysis] No topic ID provided')
return
}
// 获取当前记忆状态
const memoryState = store.getState().memory || {}
const messagesState = store.getState().messages || {}
const shortMemoryAnalyzeModel = memoryState.shortMemoryAnalyzeModel
if (!shortMemoryAnalyzeModel) {
console.log('[Short Memory Analysis] No short memory analyze model set')
return
}
// 获取对话内容
let messages: any[] = []
// 从 Redux store 中获取话题消息
if (messagesState.messagesByTopic && messagesState.messagesByTopic[topicId]) {
messages = messagesState.messagesByTopic[topicId] || []
} else {
// 如果 Redux store 中没有,则从数据库中获取
try {
const topicMessages = await TopicManager.getTopicMessages(topicId)
if (topicMessages && topicMessages.length > 0) {
messages = topicMessages
}
} catch (error) {
console.error(`[Short Memory Analysis] Failed to get messages for topic ${topicId}:`, error)
return
}
}
if (!messages || messages.length === 0) {
console.log('[Short Memory Analysis] No messages to analyze.')
return
}
// 获取现有的短期记忆
const existingShortMemories = store.getState().memory?.shortMemories || []
const topicShortMemories = existingShortMemories.filter((memory) => memory.topicId === topicId)
// 收集所有已分析过的消息ID
const analyzedMessageIds = new Set<string>()
topicShortMemories.forEach((memory) => {
if (memory.analyzedMessageIds) {
memory.analyzedMessageIds.forEach((id) => analyzedMessageIds.add(id))
}
})
// 找出未分析过的新消息
const newMessages = messages.filter((msg) => !analyzedMessageIds.has(msg.id))
if (newMessages.length === 0) {
console.log('[Short Memory Analysis] No new messages to analyze.')
return
}
console.log(`[Short Memory Analysis] Found ${newMessages.length} new messages to analyze.`)
// 构建新消息的对话内容
const newConversation = newMessages.map((msg) => `${msg.role || 'user'}: ${msg.content || ''}`).join('\n')
// 获取已有的短期记忆内容
const existingMemoriesContent = topicShortMemories.map((memory) => memory.content).join('\n')
try {
console.log('[Short Memory Analysis] Starting analysis...')
console.log(`[Short Memory Analysis] Analyzing topic: ${topicId}`)
console.log('[Short Memory Analysis] New conversation length:', newConversation.length)
// 构建短期记忆分析提示词,包含已有记忆和新对话
const prompt = `
1.
2. AI回复中的重要内容和对用户问题的解决方案
3.
4.
5.
${
existingMemoriesContent
? `以下是已经提取的重要信息:
${existingMemoriesContent}
`
: '请对对话进行全面分析,确保不遗漏任何重要细节。每条总结应该是完整的句子,清晰表达一个重要的上下文信息。'
}
-
-
-
-
- 5-10
- 5
:
${newConversation}
`
// 获取模型
const model = store
.getState()
.llm.providers.flatMap((provider) => provider.models)
.find((model) => model.id === shortMemoryAnalyzeModel)
if (!model) {
console.error(`[Short Memory Analysis] Model ${shortMemoryAnalyzeModel} not found`)
return
}
// 调用AI生成文本
console.log('[Short Memory Analysis] Calling AI.generateText...')
const result = await fetchGenerate({
prompt: prompt,
content: newConversation,
modelId: shortMemoryAnalyzeModel
})
console.log('[Short Memory Analysis] AI.generateText response:', result)
if (!result) {
console.log('[Short Memory Analysis] No result from AI analysis.')
return
}
// 解析结果
const lines = result
.split('\n')
.map((line: string) => line.trim())
.filter((line: string) => {
// 匹配以数字和点开头的行(如"1.", "2.")或者以短横线开头的行(如"-"
return /^\d+\./.test(line) || line.startsWith('-')
})
.map((line: string) => {
// 如果是数字开头,移除数字和点,如果是短横线开头,移除短横线
if (/^\d+\./.test(line)) {
return line.replace(/^\d+\.\s*/, '').trim()
} else if (line.startsWith('-')) {
return line.substring(1).trim()
}
return line
})
.filter(Boolean)
console.log('[Short Memory Analysis] Extracted items:', lines)
// 过滤掉已存在的记忆
const existingContents = topicShortMemories.map((memory) => memory.content.toLowerCase())
const newMemories = lines.filter((content: string) => !existingContents.includes(content.toLowerCase()))
console.log(`[Short Memory Analysis] Found ${lines.length} items, ${newMemories.length} are new`)
// 收集新分析的消息ID
const newMessageIds = newMessages.map((msg) => msg.id)
// 获取最后一条消息的ID用于跟踪分析进度
const lastMessageId = messages[messages.length - 1]?.id
// 添加新的短期记忆
for (const content of newMemories) {
addShortMemoryItem(content, topicId, newMessageIds, lastMessageId)
console.log(`[Short Memory Analysis] Added new short memory: "${content}" to topic ${topicId}`)
}
return newMemories.length > 0
} catch (error) {
console.error('[Short Memory Analysis] Failed to analyze and add short memories:', error)
return false
}
}
// 将记忆应用到系统提示词
import { persistor } from '@renderer/store' // Import persistor
@ -299,8 +547,13 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
const state = store.getState() // Use imported store
// 确保 state.memory 存在,如果不存在则提供默认值
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories } = state.memory ||
{ isActive: false, memories: [], memoryLists: [], shortMemoryActive: false, shortMemories: [] }
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories } = state.memory || {
isActive: false,
memories: [],
memoryLists: [],
shortMemoryActive: false,
shortMemories: []
}
// 获取当前话题ID
const currentTopicId = state.messages.currentTopic?.id
@ -320,10 +573,10 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
// 处理短记忆
if (shortMemoryActive && shortMemories && shortMemories.length > 0 && currentTopicId) {
// 获取当前话题的短记忆
const topicShortMemories = shortMemories.filter(memory => memory.topicId === currentTopicId)
const topicShortMemories = shortMemories.filter((memory) => memory.topicId === currentTopicId)
if (topicShortMemories.length > 0) {
const shortMemoryPrompt = topicShortMemories.map(memory => `- ${memory.content}`).join('\n')
const shortMemoryPrompt = topicShortMemories.map((memory) => `- ${memory.content}`).join('\n')
console.log('[Memory] Short memory prompt:', shortMemoryPrompt)
// 添加短记忆到提示词

View File

@ -19,6 +19,9 @@ export interface Memory {
source?: string // 来源,例如"自动分析"或"手动添加"
category?: string // 分类,例如"用户偏好"、"技术需求"等
listId: string // 所属的记忆列表ID
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
lastMessageId?: string // 分析时的最后一条消息的ID用于跟踪分析进度
topicId?: string // 关联的对话话题ID用于跟踪该记忆来自哪个话题
}
// 短记忆项接口
@ -27,6 +30,8 @@ export interface ShortMemory {
content: string
createdAt: string
topicId: string // 关联的对话话题ID
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
lastMessageId?: string // 分析时的最后一条消息的ID用于跟踪分析进度
}
export interface MemoryState {
@ -37,7 +42,8 @@ export interface MemoryState {
isActive: boolean // 记忆功能是否激活
shortMemoryActive: boolean // 短记忆功能是否激活
autoAnalyze: boolean // 是否自动分析
analyzeModel: string | null // 用于分析的模型ID
analyzeModel: string | null // 用于长期记忆分析的模型ID
shortMemoryAnalyzeModel: string | null // 用于短期记忆分析的模型ID
lastAnalyzeTime: number | null // 上次分析时间
isAnalyzing: boolean // 是否正在分析
}
@ -60,7 +66,8 @@ const initialState: MemoryState = {
isActive: true,
shortMemoryActive: true, // 默认启用短记忆功能
autoAnalyze: true,
analyzeModel: 'gpt-3.5-turbo', // 设置默认模型
analyzeModel: 'gpt-3.5-turbo', // 设置默认长期记忆分析模型
shortMemoryAnalyzeModel: 'gpt-3.5-turbo', // 设置默认短期记忆分析模型
lastAnalyzeTime: null,
isAnalyzing: false
}
@ -72,7 +79,15 @@ const memorySlice = createSlice({
// 添加新记忆
addMemory: (
state,
action: PayloadAction<{ content: string; source?: string; category?: string; listId?: string }>
action: PayloadAction<{
content: string
source?: string
category?: string
listId?: string
analyzedMessageIds?: string[]
lastMessageId?: string
topicId?: string
}>
) => {
// 确保 memoryLists 存在
if (!state.memoryLists) {
@ -91,7 +106,10 @@ const memorySlice = createSlice({
createdAt: new Date().toISOString(),
source: action.payload.source || '手动添加',
category: action.payload.category,
listId: listId
listId: listId,
analyzedMessageIds: action.payload.analyzedMessageIds,
lastMessageId: action.payload.lastMessageId,
topicId: action.payload.topicId
}
// 确保 memories 存在
@ -141,11 +159,16 @@ const memorySlice = createSlice({
state.autoAnalyze = action.payload
},
// 设置分析模型
// 设置长期记忆分析模型
setAnalyzeModel: (state, action: PayloadAction<string | null>) => {
state.analyzeModel = action.payload
},
// 设置短期记忆分析模型
setShortMemoryAnalyzeModel: (state, action: PayloadAction<string | null>) => {
state.shortMemoryAnalyzeModel = action.payload
},
// 设置分析状态
setAnalyzing: (state, action: PayloadAction<boolean>) => {
state.isAnalyzing = action.payload
@ -285,12 +308,22 @@ const memorySlice = createSlice({
},
// 添加短记忆
addShortMemory: (state, action: PayloadAction<{ content: string; topicId: string }>) => {
addShortMemory: (
state,
action: PayloadAction<{
content: string
topicId: string
analyzedMessageIds?: string[]
lastMessageId?: string
}>
) => {
const newShortMemory: ShortMemory = {
id: nanoid(),
content: action.payload.content,
createdAt: new Date().toISOString(),
topicId: action.payload.topicId
topicId: action.payload.topicId,
analyzedMessageIds: action.payload.analyzedMessageIds,
lastMessageId: action.payload.lastMessageId
}
// 确保 shortMemories 存在
@ -345,6 +378,7 @@ export const {
setMemoryActive,
setAutoAnalyze,
setAnalyzeModel,
setShortMemoryAnalyzeModel,
setAnalyzing,
importMemories,
clearMemories,