mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 21:35:52 +08:00
添加了记忆功能
This commit is contained in:
parent
07bfb8e01e
commit
09e6871118
195
src/renderer/src/components/Popups/ShortMemoryPopup.tsx
Normal file
195
src/renderer/src/components/Popups/ShortMemoryPopup.tsx
Normal 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
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1047,12 +1047,74 @@
|
|||||||
"analyzeNow": "Analyze Now",
|
"analyzeNow": "Analyze Now",
|
||||||
"startingAnalysis": "Starting analysis...",
|
"startingAnalysis": "Starting analysis...",
|
||||||
"cannotAnalyze": "Cannot analyze, please check settings",
|
"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",
|
"selectTopic": "Select Topic",
|
||||||
"selectTopicPlaceholder": "Select a topic to analyze",
|
"selectTopicPlaceholder": "Select a topic to analyze",
|
||||||
"filterByCategory": "Filter by Category",
|
"filterByCategory": "Filter by Category",
|
||||||
"allCategories": "All",
|
"allCategories": "All",
|
||||||
"uncategorized": "Uncategorized",
|
"uncategorized": "Uncategorized",
|
||||||
"shortMemory": "Short-term Memory",
|
"shortMemory": "Short-term Memory",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"longMemory": "Long-term Memory",
|
||||||
"toggleShortMemoryActive": "Toggle Short-term Memory",
|
"toggleShortMemoryActive": "Toggle Short-term Memory",
|
||||||
"addShortMemory": "Add Short-term Memory",
|
"addShortMemory": "Add Short-term Memory",
|
||||||
"addShortMemoryPlaceholder": "Enter short-term memory content, only valid in current conversation",
|
"addShortMemoryPlaceholder": "Enter short-term memory content, only valid in current conversation",
|
||||||
@ -1060,22 +1122,7 @@
|
|||||||
"noCurrentTopic": "Please select a conversation topic first",
|
"noCurrentTopic": "Please select a conversation topic first",
|
||||||
"confirmDelete": "Confirm Delete",
|
"confirmDelete": "Confirm Delete",
|
||||||
"confirmDeleteContent": "Are you sure you want to delete this short-term memory?",
|
"confirmDeleteContent": "Are you sure you want to delete this short-term memory?",
|
||||||
"delete": "Delete",
|
"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}} 中的所有记忆吗?此操作不可恢复。"
|
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
|
|||||||
@ -1353,57 +1353,58 @@
|
|||||||
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
|
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "[to be translated]:记忆功能",
|
"title": "メモリー機能",
|
||||||
"description": "[to be translated]:管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
"description": "AIアシスタントの長期メモリーを管理し、会話を自動分析して重要な情報を抽出します",
|
||||||
"enableMemory": "[to be translated]:启用记忆功能",
|
"enableMemory": "メモリー機能を有効にする",
|
||||||
"enableAutoAnalyze": "[to be translated]:启用自动分析",
|
"enableAutoAnalyze": "自動分析を有効にする",
|
||||||
"analyzeModel": "[to be translated]:分析模型",
|
"analyzeModel": "分析モデル",
|
||||||
"selectModel": "[to be translated]:选择模型",
|
"selectModel": "モデルを選択",
|
||||||
"memoriesList": "[to be translated]:记忆列表",
|
"memoriesList": "メモリーリスト",
|
||||||
"memoryLists": "[to be translated]:记忆角色",
|
"memoryLists": "メモリーロール",
|
||||||
"addMemory": "[to be translated]:添加记忆",
|
"addMemory": "メモリーを追加",
|
||||||
"editMemory": "[to be translated]:编辑记忆",
|
"editMemory": "メモリーを編集",
|
||||||
"clearAll": "[to be translated]:清空全部",
|
"clearAll": "すべてクリア",
|
||||||
"noMemories": "[to be translated]:暂无记忆",
|
"noMemories": "メモリーなし",
|
||||||
"memoryPlaceholder": "[to be translated]:输入要记住的内容",
|
"memoryPlaceholder": "記憶したい内容を入力",
|
||||||
"addSuccess": "[to be translated]:记忆添加成功",
|
"addSuccess": "メモリーが正常に追加されました",
|
||||||
"editSuccess": "[to be translated]:记忆编辑成功",
|
"editSuccess": "メモリーが正常に編集されました",
|
||||||
"deleteSuccess": "[to be translated]:记忆删除成功",
|
"deleteSuccess": "メモリーが正常に削除されました",
|
||||||
"clearSuccess": "[to be translated]:记忆清空成功",
|
"clearSuccess": "メモリーが正常にクリアされました",
|
||||||
"clearConfirmTitle": "[to be translated]:确认清空",
|
"clearConfirmTitle": "クリアの確認",
|
||||||
"clearConfirmContent": "[to be translated]:确定要清空所有记忆吗?此操作无法撤销。",
|
"clearConfirmContent": "すべてのメモリーをクリアしますか?この操作は元に戻せません。",
|
||||||
"listView": "[to be translated]:列表视图",
|
"listView": "リスト表示",
|
||||||
"mindmapView": "[to be translated]:思维导图",
|
"mindmapView": "マインドマップ表示",
|
||||||
"centerNodeLabel": "[to be translated]:用户记忆",
|
"centerNodeLabel": "ユーザーメモリー",
|
||||||
"manualAnalyze": "[to be translated]:手动分析",
|
"manualAnalyze": "手動分析",
|
||||||
"analyzeNow": "[to be translated]:立即分析",
|
"analyzeNow": "今すぐ分析",
|
||||||
"startingAnalysis": "[to be translated]:开始分析...",
|
"startingAnalysis": "分析開始...",
|
||||||
"cannotAnalyze": "[to be translated]:无法分析,请检查设置",
|
"cannotAnalyze": "分析できません、設定を確認してください",
|
||||||
"selectTopic": "[to be translated]:选择话题",
|
"selectTopic": "トピックを選択",
|
||||||
"selectTopicPlaceholder": "[to be translated]:选择要分析的话题",
|
"selectTopicPlaceholder": "分析するトピックを選択",
|
||||||
"filterByCategory": "[to be translated]:按分类筛选",
|
"filterByCategory": "カテゴリーで絞り込み",
|
||||||
"allCategories": "[to be translated]:全部",
|
"allCategories": "すべて",
|
||||||
"uncategorized": "[to be translated]:未分类",
|
"uncategorized": "未分類",
|
||||||
"addList": "[to be translated]:添加记忆列表",
|
"addList": "メモリーリストを追加",
|
||||||
"editList": "[to be translated]:编辑记忆列表",
|
"editList": "メモリーリストを編集",
|
||||||
"listName": "[to be translated]:列表名称",
|
"listName": "リスト名",
|
||||||
"listNamePlaceholder": "[to be translated]:输入列表名称",
|
"listNamePlaceholder": "リスト名を入力",
|
||||||
"listDescription": "[to be translated]:列表描述",
|
"listDescription": "リストの説明",
|
||||||
"listDescriptionPlaceholder": "[to be translated]:输入列表描述(可选)",
|
"listDescriptionPlaceholder": "リストの説明を入力(オプション)",
|
||||||
"noLists": "[to be translated]:暂无记忆列表",
|
"noLists": "メモリーリストなし",
|
||||||
"confirmDeleteList": "[to be translated]:确认删除列表",
|
"confirmDeleteList": "リスト削除の確認",
|
||||||
"confirmDeleteListContent": "[to be translated]:确定要删除 {{name}} 列表吗?此操作将同时删除列表中的所有记忆,且不可恢复。",
|
"confirmDeleteListContent": "{{name}} リストを削除しますか?この操作はリスト内のすべてのメモリーも削除し、元に戻せません。",
|
||||||
"toggleActive": "[to be translated]:切换激活状态",
|
"toggleActive": "アクティブ状態を切り替え",
|
||||||
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
"clearConfirmContentList": "{{name}} のすべてのメモリーをクリアしますか?この操作は元に戻せません。",
|
||||||
"shortMemory": "[to be translated]:短期记忆",
|
"shortMemory": "短期メモリー",
|
||||||
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
|
"longMemory": "長期メモリー",
|
||||||
"addShortMemory": "[to be translated]:添加短期记忆",
|
"toggleShortMemoryActive": "短期メモリー機能を切り替え",
|
||||||
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
|
"addShortMemory": "短期メモリーを追加",
|
||||||
"noShortMemories": "[to be translated]:暂无短期记忆",
|
"addShortMemoryPlaceholder": "短期メモリーの内容を入力、現在の会話のみ有効",
|
||||||
"noCurrentTopic": "[to be translated]:请先选择一个对话话题",
|
"noShortMemories": "短期メモリーなし",
|
||||||
"confirmDelete": "[to be translated]:确认删除",
|
"noCurrentTopic": "まず会話トピックを選択してください",
|
||||||
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
|
"confirmDelete": "削除の確認",
|
||||||
"delete": "[to be translated]:删除"
|
"confirmDeleteContent": "この短期メモリーを削除しますか?",
|
||||||
|
"delete": "削除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
|
|||||||
@ -1396,6 +1396,7 @@
|
|||||||
"toggleActive": "[to be translated]:切换激活状态",
|
"toggleActive": "[to be translated]:切换激活状态",
|
||||||
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
||||||
"shortMemory": "[to be translated]:短期记忆",
|
"shortMemory": "[to be translated]:短期记忆",
|
||||||
|
"longMemory": "[to be translated]:长期记忆",
|
||||||
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
|
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
|
||||||
"addShortMemory": "[to be translated]:添加短期记忆",
|
"addShortMemory": "[to be translated]:添加短期记忆",
|
||||||
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
|
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
|
||||||
|
|||||||
@ -1029,7 +1029,8 @@
|
|||||||
"description": "管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
"description": "管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
||||||
"enableMemory": "启用记忆功能",
|
"enableMemory": "启用记忆功能",
|
||||||
"enableAutoAnalyze": "启用自动分析",
|
"enableAutoAnalyze": "启用自动分析",
|
||||||
"analyzeModel": "分析模型",
|
"analyzeModel": "长期记忆分析模型",
|
||||||
|
"shortMemoryAnalyzeModel": "短期记忆分析模型",
|
||||||
"selectModel": "选择模型",
|
"selectModel": "选择模型",
|
||||||
"memoriesList": "记忆列表",
|
"memoriesList": "记忆列表",
|
||||||
"memoryLists": "记忆角色",
|
"memoryLists": "记忆角色",
|
||||||
@ -1051,6 +1052,66 @@
|
|||||||
"analyzeNow": "立即分析",
|
"analyzeNow": "立即分析",
|
||||||
"startingAnalysis": "开始分析...",
|
"startingAnalysis": "开始分析...",
|
||||||
"cannotAnalyze": "无法分析,请检查设置",
|
"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": "选择话题",
|
"selectTopic": "选择话题",
|
||||||
"selectTopicPlaceholder": "选择要分析的话题",
|
"selectTopicPlaceholder": "选择要分析的话题",
|
||||||
"filterByCategory": "按分类筛选",
|
"filterByCategory": "按分类筛选",
|
||||||
@ -1068,6 +1129,12 @@
|
|||||||
"toggleActive": "切换激活状态",
|
"toggleActive": "切换激活状态",
|
||||||
"clearConfirmContentList": "确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
"clearConfirmContentList": "确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
||||||
"shortMemory": "短期记忆",
|
"shortMemory": "短期记忆",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"longMemory": "长期记忆",
|
||||||
|
"shortMemorySettings": "短期记忆设置",
|
||||||
|
"shortMemoryDescription": "管理与当前对话相关的短期记忆",
|
||||||
|
"longMemorySettings": "长期记忆设置",
|
||||||
|
"longMemoryDescription": "管理跨对话的长期记忆",
|
||||||
"toggleShortMemoryActive": "切换短期记忆功能",
|
"toggleShortMemoryActive": "切换短期记忆功能",
|
||||||
"addShortMemory": "添加短期记忆",
|
"addShortMemory": "添加短期记忆",
|
||||||
"addShortMemoryPlaceholder": "输入短期记忆内容,只在当前对话中有效",
|
"addShortMemoryPlaceholder": "输入短期记忆内容,只在当前对话中有效",
|
||||||
@ -1075,7 +1142,9 @@
|
|||||||
"noCurrentTopic": "请先选择一个对话话题",
|
"noCurrentTopic": "请先选择一个对话话题",
|
||||||
"confirmDelete": "确认删除",
|
"confirmDelete": "确认删除",
|
||||||
"confirmDeleteContent": "确定要删除这条短期记忆吗?",
|
"confirmDeleteContent": "确定要删除这条短期记忆吗?",
|
||||||
"delete": "删除"
|
"delete": "删除",
|
||||||
|
"allTopics": "所有话题",
|
||||||
|
"noTopics": "没有话题"
|
||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"actions": "操作",
|
"actions": "操作",
|
||||||
|
|||||||
@ -1353,57 +1353,58 @@
|
|||||||
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
|
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "[to be translated]:记忆功能",
|
"title": "記憶功能",
|
||||||
"description": "[to be translated]:管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
"description": "管理AI助手的長期記憶,自動分析對話並提取重要信息",
|
||||||
"enableMemory": "[to be translated]:启用记忆功能",
|
"enableMemory": "啟用記憶功能",
|
||||||
"enableAutoAnalyze": "[to be translated]:启用自动分析",
|
"enableAutoAnalyze": "啟用自動分析",
|
||||||
"analyzeModel": "[to be translated]:分析模型",
|
"analyzeModel": "分析模型",
|
||||||
"selectModel": "[to be translated]:选择模型",
|
"selectModel": "選擇模型",
|
||||||
"memoriesList": "[to be translated]:记忆列表",
|
"memoriesList": "記憶列表",
|
||||||
"memoryLists": "[to be translated]:记忆角色",
|
"memoryLists": "記憶角色",
|
||||||
"addMemory": "[to be translated]:添加记忆",
|
"addMemory": "添加記憶",
|
||||||
"editMemory": "[to be translated]:编辑记忆",
|
"editMemory": "編輯記憶",
|
||||||
"clearAll": "[to be translated]:清空全部",
|
"clearAll": "清空全部",
|
||||||
"noMemories": "[to be translated]:暂无记忆",
|
"noMemories": "暫無記憶",
|
||||||
"memoryPlaceholder": "[to be translated]:输入要记住的内容",
|
"memoryPlaceholder": "輸入要記住的內容",
|
||||||
"addSuccess": "[to be translated]:记忆添加成功",
|
"addSuccess": "記憶添加成功",
|
||||||
"editSuccess": "[to be translated]:记忆编辑成功",
|
"editSuccess": "記憶編輯成功",
|
||||||
"deleteSuccess": "[to be translated]:记忆删除成功",
|
"deleteSuccess": "記憶刪除成功",
|
||||||
"clearSuccess": "[to be translated]:记忆清空成功",
|
"clearSuccess": "記憶清空成功",
|
||||||
"clearConfirmTitle": "[to be translated]:确认清空",
|
"clearConfirmTitle": "確認清空",
|
||||||
"clearConfirmContent": "[to be translated]:确定要清空所有记忆吗?此操作无法撤销。",
|
"clearConfirmContent": "確定要清空所有記憶嗎?此操作無法撤銷。",
|
||||||
"listView": "[to be translated]:列表视图",
|
"listView": "列表視圖",
|
||||||
"mindmapView": "[to be translated]:思维导图",
|
"mindmapView": "思維導圖",
|
||||||
"centerNodeLabel": "[to be translated]:用户记忆",
|
"centerNodeLabel": "用戶記憶",
|
||||||
"manualAnalyze": "[to be translated]:手动分析",
|
"manualAnalyze": "手動分析",
|
||||||
"analyzeNow": "[to be translated]:立即分析",
|
"analyzeNow": "立即分析",
|
||||||
"startingAnalysis": "[to be translated]:开始分析...",
|
"startingAnalysis": "開始分析...",
|
||||||
"cannotAnalyze": "[to be translated]:无法分析,请检查设置",
|
"cannotAnalyze": "無法分析,請檢查設置",
|
||||||
"selectTopic": "[to be translated]:选择话题",
|
"selectTopic": "選擇話題",
|
||||||
"selectTopicPlaceholder": "[to be translated]:选择要分析的话题",
|
"selectTopicPlaceholder": "選擇要分析的話題",
|
||||||
"filterByCategory": "[to be translated]:按分类筛选",
|
"filterByCategory": "按分類篩選",
|
||||||
"allCategories": "[to be translated]:全部",
|
"allCategories": "全部",
|
||||||
"uncategorized": "[to be translated]:未分类",
|
"uncategorized": "未分類",
|
||||||
"addList": "[to be translated]:添加记忆列表",
|
"addList": "添加記憶列表",
|
||||||
"editList": "[to be translated]:编辑记忆列表",
|
"editList": "編輯記憶列表",
|
||||||
"listName": "[to be translated]:列表名称",
|
"listName": "列表名稱",
|
||||||
"listNamePlaceholder": "[to be translated]:输入列表名称",
|
"listNamePlaceholder": "輸入列表名稱",
|
||||||
"listDescription": "[to be translated]:列表描述",
|
"listDescription": "列表描述",
|
||||||
"listDescriptionPlaceholder": "[to be translated]:输入列表描述(可选)",
|
"listDescriptionPlaceholder": "輸入列表描述(可選)",
|
||||||
"noLists": "[to be translated]:暂无记忆列表",
|
"noLists": "暫無記憶列表",
|
||||||
"confirmDeleteList": "[to be translated]:确认删除列表",
|
"confirmDeleteList": "確認刪除列表",
|
||||||
"confirmDeleteListContent": "[to be translated]:确定要删除 {{name}} 列表吗?此操作将同时删除列表中的所有记忆,且不可恢复。",
|
"confirmDeleteListContent": "確定要刪除 {{name}} 列表嗎?此操作將同時刪除列表中的所有記憶,且不可恢復。",
|
||||||
"toggleActive": "[to be translated]:切换激活状态",
|
"toggleActive": "切換激活狀態",
|
||||||
"clearConfirmContentList": "[to be translated]:确定要清空 {{name}} 中的所有记忆吗?此操作不可恢复。",
|
"clearConfirmContentList": "確定要清空 {{name}} 中的所有記憶嗎?此操作不可恢復。",
|
||||||
"shortMemory": "[to be translated]:短期记忆",
|
"shortMemory": "短期記憶",
|
||||||
"toggleShortMemoryActive": "[to be translated]:切换短期记忆功能",
|
"longMemory": "長期記憶",
|
||||||
"addShortMemory": "[to be translated]:添加短期记忆",
|
"toggleShortMemoryActive": "切換短期記憶功能",
|
||||||
"addShortMemoryPlaceholder": "[to be translated]:输入短期记忆内容,只在当前对话中有效",
|
"addShortMemory": "添加短期記憶",
|
||||||
"noShortMemories": "[to be translated]:暂无短期记忆",
|
"addShortMemoryPlaceholder": "輸入短期記憶內容,只在當前對話中有效",
|
||||||
"noCurrentTopic": "[to be translated]:请先选择一个对话话题",
|
"noShortMemories": "暫無短期記憶",
|
||||||
"confirmDelete": "[to be translated]:确认删除",
|
"noCurrentTopic": "請先選擇一個對話話題",
|
||||||
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
|
"confirmDelete": "確認刪除",
|
||||||
"delete": "[to be translated]:删除"
|
"confirmDeleteContent": "確定要刪除這條短期記憶嗎?",
|
||||||
|
"delete": "刪除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
|
|||||||
@ -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 { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover'
|
import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover'
|
||||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||||
|
import ShortMemoryPopup from '@renderer/components/Popups/ShortMemoryPopup'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||||
@ -10,10 +11,11 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
|||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
|
import { analyzeAndAddShortMemories } from '@renderer/services/MemoryService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setNarrowMode } from '@renderer/store/settings'
|
import { setNarrowMode } from '@renderer/store/settings'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -27,7 +29,7 @@ interface Props {
|
|||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
const HeaderNavbar: FC<Props> = ({ activeAssistant, activeTopic }) => {
|
||||||
const { assistant } = useAssistant(activeAssistant.id)
|
const { assistant } = useAssistant(activeAssistant.id)
|
||||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||||
const { topicPosition, sidebarIcons, narrowMode } = useSettings()
|
const { topicPosition, sidebarIcons, narrowMode } = useSettings()
|
||||||
@ -55,6 +57,28 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
|||||||
dispatch(setNarrowMode(!narrowMode))
|
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 (
|
return (
|
||||||
<Navbar className="home-navbar">
|
<Navbar className="home-navbar">
|
||||||
{showAssistants && (
|
{showAssistants && (
|
||||||
@ -86,6 +110,14 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
<HStack alignItems="center" gap={8}>
|
<HStack alignItems="center" gap={8}>
|
||||||
<UpdateAppButton />
|
<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}>
|
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
||||||
<NarrowIcon onClick={() => SearchPopup.show()}>
|
<NarrowIcon onClick={() => SearchPopup.show()}>
|
||||||
<SearchOutlined />
|
<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
|
export default HeaderNavbar
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Handle, Position } from '@xyflow/react';
|
import { Handle, Position } from '@xyflow/react'
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Typography } from 'antd'
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface CenterNodeProps {
|
interface CenterNodeProps {
|
||||||
data: {
|
data: {
|
||||||
label: string;
|
label: string
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CenterNode: React.FC<CenterNodeProps> = ({ data }) => {
|
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.Left} id="l" />
|
||||||
<Handle type="source" position={Position.Top} id="t" />
|
<Handle type="source" position={Position.Top} id="t" />
|
||||||
</NodeContainer>
|
</NodeContainer>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
const NodeContainer = styled.div`
|
const NodeContainer = styled.div`
|
||||||
width: 150px;
|
width: 150px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
`;
|
`
|
||||||
|
|
||||||
export default CenterNode;
|
export default CenterNode
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
@ -1,19 +1,19 @@
|
|||||||
import { DeleteOutlined, EditOutlined, TagOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined, TagOutlined } from '@ant-design/icons'
|
||||||
import { Memory } from '@renderer/store/memory';
|
import { Memory } from '@renderer/store/memory'
|
||||||
import { Button, Card, Tag, Tooltip, Typography } from 'antd';
|
import { Handle, Position } from '@xyflow/react'
|
||||||
import { Handle, Position } from '@xyflow/react';
|
import { Button, Card, Tag, Tooltip, Typography } from 'antd'
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface MemoryNodeProps {
|
interface MemoryNodeProps {
|
||||||
data: {
|
data: {
|
||||||
memory: Memory;
|
memory: Memory
|
||||||
onEdit: (id: string) => void;
|
onEdit: (id: string) => void
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MemoryNode: React.FC<MemoryNodeProps> = ({ data }) => {
|
const MemoryNode: React.FC<MemoryNodeProps> = ({ data }) => {
|
||||||
const { memory, onEdit, onDelete } = data;
|
const { memory, onEdit, onDelete } = data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeContainer>
|
<NodeContainer>
|
||||||
@ -35,43 +35,31 @@ const MemoryNode: React.FC<MemoryNodeProps> = ({ data }) => {
|
|||||||
extra={
|
extra={
|
||||||
<div>
|
<div>
|
||||||
<Tooltip title="编辑">
|
<Tooltip title="编辑">
|
||||||
<Button
|
<Button icon={<EditOutlined />} type="text" size="small" onClick={() => onEdit(memory.id)} />
|
||||||
icon={<EditOutlined />}
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
onClick={() => onEdit(memory.id)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="删除">
|
<Tooltip title="删除">
|
||||||
<Button
|
<Button icon={<DeleteOutlined />} type="text" danger size="small" onClick={() => onDelete(memory.id)} />
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
type="text"
|
|
||||||
danger
|
|
||||||
size="small"
|
|
||||||
onClick={() => onDelete(memory.id)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
}
|
}>
|
||||||
>
|
|
||||||
<MemoryMeta>
|
<MemoryMeta>
|
||||||
<span>{new Date(memory.createdAt).toLocaleString()}</span>
|
<span>{new Date(memory.createdAt).toLocaleString()}</span>
|
||||||
{memory.source && <span>{memory.source}</span>}
|
{memory.source && <span>{memory.source}</span>}
|
||||||
</MemoryMeta>
|
</MemoryMeta>
|
||||||
</Card>
|
</Card>
|
||||||
</NodeContainer>
|
</NodeContainer>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
const NodeContainer = styled.div`
|
const NodeContainer = styled.div`
|
||||||
width: 220px;
|
width: 220px;
|
||||||
`;
|
`
|
||||||
|
|
||||||
const MemoryMeta = styled.div`
|
const MemoryMeta = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
`;
|
`
|
||||||
|
|
||||||
export default MemoryNode;
|
export default MemoryNode
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -164,6 +164,8 @@ const ContentContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
height: calc(100vh - var(--navbar-height)); /* 设置高度为视口高度减去导航栏高度 */
|
||||||
|
overflow: hidden; /* 防止内容溢出 */
|
||||||
`
|
`
|
||||||
|
|
||||||
const SettingMenus = styled.ul`
|
const SettingMenus = styled.ul`
|
||||||
@ -173,6 +175,26 @@ const SettingMenus = styled.ul`
|
|||||||
border-right: 0.5px solid var(--color-border);
|
border-right: 0.5px solid var(--color-border);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
user-select: none;
|
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)`
|
const MenuItemLink = styled(Link)`
|
||||||
@ -217,6 +239,26 @@ const SettingContent = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border-right: 0.5px solid var(--color-border);
|
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
|
export default SettingsPage
|
||||||
|
|||||||
@ -12,18 +12,49 @@ export const SettingContainer = styled.div<{ theme?: ThemeMode }>`
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: 75px;
|
padding-bottom: 75px;
|
||||||
overflow-y: scroll;
|
overflow-y: auto; /* 改为auto,只在需要时显示滚动条 */
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
background: ${(props) => (props.theme === 'dark' ? 'transparent' : 'var(--color-background-soft)')};
|
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 {
|
&::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 10px;
|
||||||
height: 8px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: var(--color-border);
|
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 {
|
&::-webkit-scrollbar-track {
|
||||||
|
|||||||
@ -37,7 +37,15 @@ export default abstract class BaseProvider {
|
|||||||
abstract summaries(messages: Message[], assistant: Assistant): Promise<string>
|
abstract summaries(messages: Message[], assistant: Assistant): Promise<string>
|
||||||
abstract summaryForSearch(messages: Message[], assistant: Assistant): Promise<string | null>
|
abstract summaryForSearch(messages: Message[], assistant: Assistant): Promise<string | null>
|
||||||
abstract suggestions(messages: Message[], assistant: Assistant): Promise<Suggestion[]>
|
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 check(model: Model): Promise<{ valid: boolean; error: Error | null }>
|
||||||
abstract models(): Promise<OpenAI.Models.Model[]>
|
abstract models(): Promise<OpenAI.Models.Model[]>
|
||||||
abstract generateImage(params: GenerateImageParams): Promise<string[]>
|
abstract generateImage(params: GenerateImageParams): Promise<string[]>
|
||||||
|
|||||||
@ -88,7 +88,15 @@ export default class AiProvider {
|
|||||||
return this.sdk.suggestions(messages, assistant)
|
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 })
|
return this.sdk.generateText({ prompt, content, modelId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
279
src/renderer/src/services/MemoryDeduplicationService.ts
Normal file
279
src/renderer/src/services/MemoryDeduplicationService.ts
Normal 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`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,11 +11,14 @@ import { useCallback, useEffect, useRef } from 'react' // Add useRef back
|
|||||||
// 分析对话内容并提取重要信息
|
// 分析对话内容并提取重要信息
|
||||||
const analyzeConversation = async (
|
const analyzeConversation = async (
|
||||||
conversation: string,
|
conversation: string,
|
||||||
modelId: string
|
modelId: string,
|
||||||
|
customPrompt?: string
|
||||||
): Promise<Array<{ content: string; category: string }>> => {
|
): Promise<Array<{ content: string; category: string }>> => {
|
||||||
try {
|
try {
|
||||||
// 构建分析提示词
|
// 使用自定义提示词或默认提示词
|
||||||
const prompt = `
|
const prompt =
|
||||||
|
customPrompt ||
|
||||||
|
`
|
||||||
请分析以下对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
|
请分析以下对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
|
||||||
|
|
||||||
将每条信息分类并按以下格式返回:
|
将每条信息分类并按以下格式返回:
|
||||||
@ -111,17 +114,18 @@ export const useMemoryService = () => {
|
|||||||
// 增加可选的 topicId 参数,允许分析指定的话题
|
// 增加可选的 topicId 参数,允许分析指定的话题
|
||||||
const analyzeAndAddMemories = useCallback(
|
const analyzeAndAddMemories = useCallback(
|
||||||
async (topicId?: string) => {
|
async (topicId?: string) => {
|
||||||
|
// 如果没有提供话题ID,则使用当前话题
|
||||||
// 在函数执行时获取最新状态
|
// 在函数执行时获取最新状态
|
||||||
const currentState = store.getState() // Use imported store
|
const currentState = store.getState() // Use imported store
|
||||||
const memoryState = currentState.memory || {}
|
const memoryState = currentState.memory || {}
|
||||||
const messagesState = currentState.messages || {}
|
const messagesState = currentState.messages || {}
|
||||||
|
|
||||||
// 检查isAnalyzing状态是否卡住(超过5分钟)
|
// 检查isAnalyzing状态是否卡住(超过1分钟)
|
||||||
if (memoryState.isAnalyzing && memoryState.lastAnalyzeTime) {
|
if (memoryState.isAnalyzing && memoryState.lastAnalyzeTime) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const analyzeTime = memoryState.lastAnalyzeTime
|
const analyzeTime = memoryState.lastAnalyzeTime
|
||||||
if (now - analyzeTime > 5 * 60 * 1000) {
|
if (now - analyzeTime > 1 * 60 * 1000) {
|
||||||
// 5分钟超时
|
// 1分钟超时
|
||||||
console.log('[Memory Analysis] Analysis state stuck, resetting...')
|
console.log('[Memory Analysis] Analysis state stuck, resetting...')
|
||||||
dispatch(setAnalyzing(false))
|
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.')
|
console.log('[Memory Analysis] No conversation content to analyze.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,10 +207,37 @@ export const useMemoryService = () => {
|
|||||||
dispatch(setAnalyzing(true))
|
dispatch(setAnalyzing(true))
|
||||||
console.log('[Memory Analysis] Starting analysis...')
|
console.log('[Memory Analysis] Starting analysis...')
|
||||||
console.log(`[Memory Analysis] Analyzing topic: ${targetTopicId}`)
|
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)
|
console.log('[Memory Analysis] Analysis complete. Memories extracted:', memories)
|
||||||
|
|
||||||
// 添加提取的记忆
|
// 添加提取的记忆
|
||||||
@ -193,12 +257,21 @@ export const useMemoryService = () => {
|
|||||||
// 获取当前选中的列表ID
|
// 获取当前选中的列表ID
|
||||||
const currentListId = store.getState().memory?.currentListId || store.getState().memory?.memoryLists[0]?.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(
|
dispatch(
|
||||||
addMemory({
|
addMemory({
|
||||||
content: memory.content,
|
content: memory.content,
|
||||||
source: '自动分析',
|
source: '自动分析',
|
||||||
category: memory.category,
|
category: memory.category,
|
||||||
listId: currentListId
|
listId: currentListId,
|
||||||
|
analyzedMessageIds: newMessageIds,
|
||||||
|
lastMessageId: lastMessageId,
|
||||||
|
topicId: targetTopicId
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
console.log(
|
console.log(
|
||||||
@ -263,30 +336,205 @@ export const useMemoryService = () => {
|
|||||||
return { analyzeAndAddMemories }
|
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
|
// Use imported store directly
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
addShortMemory({
|
addShortMemory({
|
||||||
content,
|
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
|
import { persistor } from '@renderer/store' // Import persistor
|
||||||
|
|
||||||
@ -299,8 +547,13 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
|||||||
|
|
||||||
const state = store.getState() // Use imported store
|
const state = store.getState() // Use imported store
|
||||||
// 确保 state.memory 存在,如果不存在则提供默认值
|
// 确保 state.memory 存在,如果不存在则提供默认值
|
||||||
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories } = state.memory ||
|
const { isActive, memories, memoryLists, shortMemoryActive, shortMemories } = state.memory || {
|
||||||
{ isActive: false, memories: [], memoryLists: [], shortMemoryActive: false, shortMemories: [] }
|
isActive: false,
|
||||||
|
memories: [],
|
||||||
|
memoryLists: [],
|
||||||
|
shortMemoryActive: false,
|
||||||
|
shortMemories: []
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前话题ID
|
// 获取当前话题ID
|
||||||
const currentTopicId = state.messages.currentTopic?.id
|
const currentTopicId = state.messages.currentTopic?.id
|
||||||
@ -320,10 +573,10 @@ export const applyMemoriesToPrompt = (systemPrompt: string): string => {
|
|||||||
// 处理短记忆
|
// 处理短记忆
|
||||||
if (shortMemoryActive && shortMemories && shortMemories.length > 0 && currentTopicId) {
|
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) {
|
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)
|
console.log('[Memory] Short memory prompt:', shortMemoryPrompt)
|
||||||
|
|
||||||
// 添加短记忆到提示词
|
// 添加短记忆到提示词
|
||||||
|
|||||||
@ -19,6 +19,9 @@ export interface Memory {
|
|||||||
source?: string // 来源,例如"自动分析"或"手动添加"
|
source?: string // 来源,例如"自动分析"或"手动添加"
|
||||||
category?: string // 分类,例如"用户偏好"、"技术需求"等
|
category?: string // 分类,例如"用户偏好"、"技术需求"等
|
||||||
listId: string // 所属的记忆列表ID
|
listId: string // 所属的记忆列表ID
|
||||||
|
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
|
||||||
|
lastMessageId?: string // 分析时的最后一条消息的ID,用于跟踪分析进度
|
||||||
|
topicId?: string // 关联的对话话题ID,用于跟踪该记忆来自哪个话题
|
||||||
}
|
}
|
||||||
|
|
||||||
// 短记忆项接口
|
// 短记忆项接口
|
||||||
@ -27,6 +30,8 @@ export interface ShortMemory {
|
|||||||
content: string
|
content: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
topicId: string // 关联的对话话题ID
|
topicId: string // 关联的对话话题ID
|
||||||
|
analyzedMessageIds?: string[] // 记录该记忆是从哪些消息中分析出来的
|
||||||
|
lastMessageId?: string // 分析时的最后一条消息的ID,用于跟踪分析进度
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MemoryState {
|
export interface MemoryState {
|
||||||
@ -37,7 +42,8 @@ export interface MemoryState {
|
|||||||
isActive: boolean // 记忆功能是否激活
|
isActive: boolean // 记忆功能是否激活
|
||||||
shortMemoryActive: boolean // 短记忆功能是否激活
|
shortMemoryActive: boolean // 短记忆功能是否激活
|
||||||
autoAnalyze: boolean // 是否自动分析
|
autoAnalyze: boolean // 是否自动分析
|
||||||
analyzeModel: string | null // 用于分析的模型ID
|
analyzeModel: string | null // 用于长期记忆分析的模型ID
|
||||||
|
shortMemoryAnalyzeModel: string | null // 用于短期记忆分析的模型ID
|
||||||
lastAnalyzeTime: number | null // 上次分析时间
|
lastAnalyzeTime: number | null // 上次分析时间
|
||||||
isAnalyzing: boolean // 是否正在分析
|
isAnalyzing: boolean // 是否正在分析
|
||||||
}
|
}
|
||||||
@ -60,7 +66,8 @@ const initialState: MemoryState = {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
shortMemoryActive: true, // 默认启用短记忆功能
|
shortMemoryActive: true, // 默认启用短记忆功能
|
||||||
autoAnalyze: true,
|
autoAnalyze: true,
|
||||||
analyzeModel: 'gpt-3.5-turbo', // 设置默认模型
|
analyzeModel: 'gpt-3.5-turbo', // 设置默认长期记忆分析模型
|
||||||
|
shortMemoryAnalyzeModel: 'gpt-3.5-turbo', // 设置默认短期记忆分析模型
|
||||||
lastAnalyzeTime: null,
|
lastAnalyzeTime: null,
|
||||||
isAnalyzing: false
|
isAnalyzing: false
|
||||||
}
|
}
|
||||||
@ -72,7 +79,15 @@ const memorySlice = createSlice({
|
|||||||
// 添加新记忆
|
// 添加新记忆
|
||||||
addMemory: (
|
addMemory: (
|
||||||
state,
|
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 存在
|
// 确保 memoryLists 存在
|
||||||
if (!state.memoryLists) {
|
if (!state.memoryLists) {
|
||||||
@ -91,7 +106,10 @@ const memorySlice = createSlice({
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
source: action.payload.source || '手动添加',
|
source: action.payload.source || '手动添加',
|
||||||
category: action.payload.category,
|
category: action.payload.category,
|
||||||
listId: listId
|
listId: listId,
|
||||||
|
analyzedMessageIds: action.payload.analyzedMessageIds,
|
||||||
|
lastMessageId: action.payload.lastMessageId,
|
||||||
|
topicId: action.payload.topicId
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 memories 存在
|
// 确保 memories 存在
|
||||||
@ -141,11 +159,16 @@ const memorySlice = createSlice({
|
|||||||
state.autoAnalyze = action.payload
|
state.autoAnalyze = action.payload
|
||||||
},
|
},
|
||||||
|
|
||||||
// 设置分析模型
|
// 设置长期记忆分析模型
|
||||||
setAnalyzeModel: (state, action: PayloadAction<string | null>) => {
|
setAnalyzeModel: (state, action: PayloadAction<string | null>) => {
|
||||||
state.analyzeModel = action.payload
|
state.analyzeModel = action.payload
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 设置短期记忆分析模型
|
||||||
|
setShortMemoryAnalyzeModel: (state, action: PayloadAction<string | null>) => {
|
||||||
|
state.shortMemoryAnalyzeModel = action.payload
|
||||||
|
},
|
||||||
|
|
||||||
// 设置分析状态
|
// 设置分析状态
|
||||||
setAnalyzing: (state, action: PayloadAction<boolean>) => {
|
setAnalyzing: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isAnalyzing = action.payload
|
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 = {
|
const newShortMemory: ShortMemory = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
content: action.payload.content,
|
content: action.payload.content,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
topicId: action.payload.topicId
|
topicId: action.payload.topicId,
|
||||||
|
analyzedMessageIds: action.payload.analyzedMessageIds,
|
||||||
|
lastMessageId: action.payload.lastMessageId
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 shortMemories 存在
|
// 确保 shortMemories 存在
|
||||||
@ -345,6 +378,7 @@ export const {
|
|||||||
setMemoryActive,
|
setMemoryActive,
|
||||||
setAutoAnalyze,
|
setAutoAnalyze,
|
||||||
setAnalyzeModel,
|
setAnalyzeModel,
|
||||||
|
setShortMemoryAnalyzeModel,
|
||||||
setAnalyzing,
|
setAnalyzing,
|
||||||
importMemories,
|
importMemories,
|
||||||
clearMemories,
|
clearMemories,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user