mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 04:19:02 +08:00
111
This commit is contained in:
parent
227dc01c85
commit
877beeab43
77
check-duplicate-messages.js
Normal file
77
check-duplicate-messages.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 检查重复消息的脚本
|
||||||
|
const { app } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 获取数据库文件路径
|
||||||
|
const userDataPath = app.getPath('userData');
|
||||||
|
const dbFilePath = path.join(userDataPath, 'CherryStudio.db');
|
||||||
|
|
||||||
|
console.log('数据库文件路径:', dbFilePath);
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (fs.existsSync(dbFilePath)) {
|
||||||
|
console.log('数据库文件存在');
|
||||||
|
|
||||||
|
// 读取数据库内容
|
||||||
|
const dbContent = fs.readFileSync(dbFilePath, 'utf8');
|
||||||
|
|
||||||
|
// 解析数据库内容
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(dbContent);
|
||||||
|
|
||||||
|
// 检查topics表中的消息
|
||||||
|
if (data.topics) {
|
||||||
|
console.log('找到topics表,共有', data.topics.length, '个主题');
|
||||||
|
|
||||||
|
// 遍历每个主题
|
||||||
|
data.topics.forEach(topic => {
|
||||||
|
console.log(`检查主题: ${topic.id}`);
|
||||||
|
|
||||||
|
if (topic.messages && Array.isArray(topic.messages)) {
|
||||||
|
console.log(` 主题消息数量: ${topic.messages.length}`);
|
||||||
|
|
||||||
|
// 检查重复消息
|
||||||
|
const messageIds = new Set();
|
||||||
|
const duplicates = [];
|
||||||
|
|
||||||
|
topic.messages.forEach(message => {
|
||||||
|
if (messageIds.has(message.id)) {
|
||||||
|
duplicates.push(message.id);
|
||||||
|
} else {
|
||||||
|
messageIds.add(message.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duplicates.length > 0) {
|
||||||
|
console.log(` 发现${duplicates.length}条重复消息ID:`, duplicates);
|
||||||
|
} else {
|
||||||
|
console.log(' 未发现重复消息ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查重复的askId (对于助手消息)
|
||||||
|
const askIds = {};
|
||||||
|
topic.messages.forEach(message => {
|
||||||
|
if (message.role === 'assistant' && message.askId) {
|
||||||
|
if (!askIds[message.askId]) {
|
||||||
|
askIds[message.askId] = [];
|
||||||
|
}
|
||||||
|
askIds[message.askId].push(message.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 输出每个askId对应的助手消息数量
|
||||||
|
Object.entries(askIds).forEach(([askId, messageIds]) => {
|
||||||
|
if (messageIds.length > 1) {
|
||||||
|
console.log(` askId ${askId} 有 ${messageIds.length} 条助手消息`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析数据库内容失败:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('数据库文件不存在');
|
||||||
|
}
|
||||||
@ -137,7 +137,7 @@
|
|||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"@types/tinycolor2": "^1",
|
"@types/tinycolor2": "^1",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"analytics": "^0.8.16",
|
"analytics": "^0.8.16",
|
||||||
"antd": "^5.22.5",
|
"antd": "^5.22.5",
|
||||||
"applescript": "^1.0.0",
|
"applescript": "^1.0.0",
|
||||||
|
|||||||
@ -22,11 +22,8 @@ import FileStorage from './services/FileStorage'
|
|||||||
import { GeminiService } from './services/GeminiService'
|
import { GeminiService } from './services/GeminiService'
|
||||||
import KnowledgeService from './services/KnowledgeService'
|
import KnowledgeService from './services/KnowledgeService'
|
||||||
import mcpService from './services/MCPService'
|
import mcpService from './services/MCPService'
|
||||||
<<<<<<< HEAD
|
|
||||||
import { memoryFileService } from './services/MemoryFileService'
|
import { memoryFileService } from './services/MemoryFileService'
|
||||||
=======
|
|
||||||
import * as MsTTSService from './services/MsTTSService'
|
import * as MsTTSService from './services/MsTTSService'
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
import * as NutstoreService from './services/NutstoreService'
|
import * as NutstoreService from './services/NutstoreService'
|
||||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
||||||
@ -310,7 +307,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// search window
|
// search window
|
||||||
<<<<<<< HEAD
|
|
||||||
ipcMain.handle(IpcChannel.SearchWindow_Open, async (_, uid: string) => {
|
ipcMain.handle(IpcChannel.SearchWindow_Open, async (_, uid: string) => {
|
||||||
await searchService.openSearchWindow(uid)
|
await searchService.openSearchWindow(uid)
|
||||||
})
|
})
|
||||||
@ -337,12 +333,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.LongTermMemory_SaveData, async (_, data, forceOverwrite = false) => {
|
ipcMain.handle(IpcChannel.LongTermMemory_SaveData, async (_, data, forceOverwrite = false) => {
|
||||||
return await memoryFileService.saveLongTermData(data, forceOverwrite)
|
return await memoryFileService.saveLongTermData(data, forceOverwrite)
|
||||||
})
|
})
|
||||||
=======
|
|
||||||
ipcMain.handle(IpcChannel.SearchWindow_Open, (_, uid: string) => searchService.openSearchWindow(uid))
|
|
||||||
ipcMain.handle(IpcChannel.SearchWindow_Close, (_, uid: string) => searchService.closeSearchWindow(uid))
|
|
||||||
ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, (_, uid: string, url: string) =>
|
|
||||||
searchService.openUrlInSearchWindow(uid, url)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 注册ASR服务器IPC处理程序
|
// 注册ASR服务器IPC处理程序
|
||||||
asrServerService.registerIpcHandlers()
|
asrServerService.registerIpcHandlers()
|
||||||
@ -352,5 +342,4 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.MsTTS_Synthesize, (_, text: string, voice: string, outputFormat: string) =>
|
ipcMain.handle(IpcChannel.MsTTS_Synthesize, (_, text: string, voice: string, outputFormat: string) =>
|
||||||
MsTTSService.synthesize(text, voice, outputFormat)
|
MsTTSService.synthesize(text, voice, outputFormat)
|
||||||
)
|
)
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,7 +188,6 @@ const api = {
|
|||||||
closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
||||||
openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
memory: {
|
memory: {
|
||||||
loadData: () => ipcRenderer.invoke(IpcChannel.Memory_LoadData),
|
loadData: () => ipcRenderer.invoke(IpcChannel.Memory_LoadData),
|
||||||
saveData: (data: any) => ipcRenderer.invoke(IpcChannel.Memory_SaveData, data),
|
saveData: (data: any) => ipcRenderer.invoke(IpcChannel.Memory_SaveData, data),
|
||||||
@ -196,11 +195,10 @@ const api = {
|
|||||||
loadLongTermData: () => ipcRenderer.invoke(IpcChannel.LongTermMemory_LoadData),
|
loadLongTermData: () => ipcRenderer.invoke(IpcChannel.LongTermMemory_LoadData),
|
||||||
saveLongTermData: (data: any, forceOverwrite: boolean = false) =>
|
saveLongTermData: (data: any, forceOverwrite: boolean = false) =>
|
||||||
ipcRenderer.invoke(IpcChannel.LongTermMemory_SaveData, data, forceOverwrite)
|
ipcRenderer.invoke(IpcChannel.LongTermMemory_SaveData, data, forceOverwrite)
|
||||||
=======
|
},
|
||||||
asrServer: {
|
asrServer: {
|
||||||
startServer: () => ipcRenderer.invoke(IpcChannel.Asr_StartServer),
|
startServer: () => ipcRenderer.invoke(IpcChannel.Asr_StartServer),
|
||||||
stopServer: (pid: number) => ipcRenderer.invoke(IpcChannel.Asr_StopServer, pid)
|
stopServer: (pid: number) => ipcRenderer.invoke(IpcChannel.Asr_StopServer, pid)
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,9 @@ import store from '@renderer/store'
|
|||||||
import { deleteShortMemory } from '@renderer/store/memory'
|
import { deleteShortMemory } from '@renderer/store/memory'
|
||||||
import { Button, Card, Col, Empty, Input, List, message, Modal, Row, Statistic, Tooltip } from 'antd'
|
import { Button, Card, Col, Empty, Input, List, message, Modal, Row, Statistic, Tooltip } from 'antd'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { createSelector } from 'reselect'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
// 不再需要确认对话框
|
// 不再需要确认对话框
|
||||||
@ -36,13 +37,27 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
|
|
||||||
|
// 创建记忆选择器 - 使用createSelector进行记忆化
|
||||||
|
const selectShortMemoriesByTopicId = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
[(state) => state.memory?.shortMemories || [], (_state, topicId) => topicId],
|
||||||
|
(shortMemories, topicId) => {
|
||||||
|
return topicId ? shortMemories.filter((memory) => memory.topicId === topicId) : []
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
// 获取短记忆状态
|
// 获取短记忆状态
|
||||||
const shortMemoryActive = useAppSelector((state) => state.memory?.shortMemoryActive || false)
|
const shortMemoryActive = useAppSelector((state) => state.memory?.shortMemoryActive || false)
|
||||||
const shortMemories = useAppSelector((state) => {
|
const shortMemories = useAppSelector((state) => selectShortMemoriesByTopicId(state, topicId))
|
||||||
const allShortMemories = state.memory?.shortMemories || []
|
|
||||||
// 只显示当前话题的短记忆
|
// 获取分析统计数据
|
||||||
return topicId ? allShortMemories.filter((memory) => memory.topicId === topicId) : []
|
const totalAnalyses = useAppSelector((state) => state.memory?.analysisStats?.totalAnalyses || 0)
|
||||||
})
|
const successfulAnalyses = useAppSelector((state) => state.memory?.analysisStats?.successfulAnalyses || 0)
|
||||||
|
const successRate = totalAnalyses ? (successfulAnalyses / totalAnalyses) * 100 : 0
|
||||||
|
const avgAnalysisTime = useAppSelector((state) => state.memory?.analysisStats?.averageAnalysisTime || 0)
|
||||||
|
|
||||||
// 添加短记忆的状态
|
// 添加短记忆的状态
|
||||||
const [newMemoryContent, setNewMemoryContent] = useState('')
|
const [newMemoryContent, setNewMemoryContent] = useState('')
|
||||||
@ -185,20 +200,14 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
|
|||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t('settings.memory.totalAnalyses') || '总分析次数'}
|
title={t('settings.memory.totalAnalyses') || '总分析次数'}
|
||||||
value={store.getState().memory?.analysisStats?.totalAnalyses || 0}
|
value={totalAnalyses}
|
||||||
precision={0}
|
precision={0}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t('settings.memory.successRate') || '成功率'}
|
title={t('settings.memory.successRate') || '成功率'}
|
||||||
value={
|
value={successRate}
|
||||||
store.getState().memory?.analysisStats?.totalAnalyses
|
|
||||||
? ((store.getState().memory?.analysisStats?.successfulAnalyses || 0) /
|
|
||||||
(store.getState().memory?.analysisStats?.totalAnalyses || 1)) *
|
|
||||||
100
|
|
||||||
: 0
|
|
||||||
}
|
|
||||||
precision={1}
|
precision={1}
|
||||||
suffix="%"
|
suffix="%"
|
||||||
/>
|
/>
|
||||||
@ -206,7 +215,7 @@ const PopupContainer: React.FC<Props> = ({ topicId, resolve }) => {
|
|||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t('settings.memory.avgAnalysisTime') || '平均分析时间'}
|
title={t('settings.memory.avgAnalysisTime') || '平均分析时间'}
|
||||||
value={store.getState().memory?.analysisStats?.averageAnalysisTime || 0}
|
value={avgAnalysisTime}
|
||||||
precision={0}
|
precision={0}
|
||||||
suffix="ms"
|
suffix="ms"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1395,7 +1395,6 @@
|
|||||||
"title": "プライバシー設定",
|
"title": "プライバシー設定",
|
||||||
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
|
"enable_privacy_mode": "匿名エラーレポートとデータ統計の送信"
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "メモリー機能",
|
"title": "メモリー機能",
|
||||||
"description": "AIアシスタントの長期メモリーを管理し、会話を自動分析して重要な情報を抽出します",
|
"description": "AIアシスタントの長期メモリーを管理し、会話を自動分析して重要な情報を抽出します",
|
||||||
@ -1453,7 +1452,7 @@
|
|||||||
"totalAnalyses": "分析回数合計",
|
"totalAnalyses": "分析回数合計",
|
||||||
"successRate": "成功率",
|
"successRate": "成功率",
|
||||||
"avgAnalysisTime": "平均分析時間"
|
"avgAnalysisTime": "平均分析時間"
|
||||||
=======
|
},
|
||||||
"tts": {
|
"tts": {
|
||||||
"title": "音声合成設定",
|
"title": "音声合成設定",
|
||||||
"enable": "音声合成を有効にする",
|
"enable": "音声合成を有効にする",
|
||||||
@ -1618,7 +1617,6 @@
|
|||||||
"asr_tts_info": "音声通話は上記の音声認識(ASR)と音声合成(TTS)の設定を使用します",
|
"asr_tts_info": "音声通話は上記の音声認識(ASR)と音声合成(TTS)の設定を使用します",
|
||||||
"test": "音声通話テスト",
|
"test": "音声通話テスト",
|
||||||
"test_info": "入力ボックスの右側にある音声通話ボタンを使用してテストしてください"
|
"test_info": "入力ボックスの右側にある音声通話ボタンを使用してテストしてください"
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
|
|||||||
@ -1398,7 +1398,6 @@
|
|||||||
"title": "Настройки приватности",
|
"title": "Настройки приватности",
|
||||||
"enable_privacy_mode": "Анонимная отправка отчетов об ошибках и статистики"
|
"enable_privacy_mode": "Анонимная отправка отчетов об ошибках и статистики"
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "[to be translated]:记忆功能",
|
"title": "[to be translated]:记忆功能",
|
||||||
"description": "[to be translated]:管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
"description": "[to be translated]:管理AI助手的长期记忆,自动分析对话并提取重要信息",
|
||||||
@ -1452,7 +1451,7 @@
|
|||||||
"confirmDelete": "[to be translated]:确认删除",
|
"confirmDelete": "[to be translated]:确认删除",
|
||||||
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
|
"confirmDeleteContent": "[to be translated]:确定要删除这条短期记忆吗?",
|
||||||
"delete": "[to be translated]:删除"
|
"delete": "[to be translated]:删除"
|
||||||
=======
|
},
|
||||||
"tts": {
|
"tts": {
|
||||||
"title": "Настройки преобразования текста в речь",
|
"title": "Настройки преобразования текста в речь",
|
||||||
"enable": "Включить преобразование текста в речь",
|
"enable": "Включить преобразование текста в речь",
|
||||||
@ -1617,7 +1616,6 @@
|
|||||||
"test": "Тестировать голосовой вызов",
|
"test": "Тестировать голосовой вызов",
|
||||||
"test_info": "Используйте кнопку голосового вызова справа от поля ввода для тестирования",
|
"test_info": "Используйте кнопку голосового вызова справа от поля ввода для тестирования",
|
||||||
"welcome_message": "Здравствуйте, я ваш ИИ-ассистент. Пожалуйста, нажмите и удерживайте кнопку разговора для начала диалога."
|
"welcome_message": "Здравствуйте, я ваш ИИ-ассистент. Пожалуйста, нажмите и удерживайте кнопку разговора для начала диалога."
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
|
|||||||
@ -1395,7 +1395,6 @@
|
|||||||
"title": "隱私設定",
|
"title": "隱私設定",
|
||||||
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
|
"enable_privacy_mode": "匿名發送錯誤報告和資料統計"
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
"memory": {
|
"memory": {
|
||||||
"title": "記憶功能",
|
"title": "記憶功能",
|
||||||
"description": "管理AI助手的長期記憶,自動分析對話並提取重要信息",
|
"description": "管理AI助手的長期記憶,自動分析對話並提取重要信息",
|
||||||
@ -1453,7 +1452,7 @@
|
|||||||
"totalAnalyses": "總分析次數",
|
"totalAnalyses": "總分析次數",
|
||||||
"successRate": "成功率",
|
"successRate": "成功率",
|
||||||
"avgAnalysisTime": "平均分析時間"
|
"avgAnalysisTime": "平均分析時間"
|
||||||
=======
|
},
|
||||||
"tts": {
|
"tts": {
|
||||||
"title": "語音設定",
|
"title": "語音設定",
|
||||||
"enable": "啟用語音合成",
|
"enable": "啟用語音合成",
|
||||||
@ -1618,7 +1617,6 @@
|
|||||||
"test": "測試通話",
|
"test": "測試通話",
|
||||||
"test_info": "請使用輸入框右側的語音通話按鈕進行測試",
|
"test_info": "請使用輸入框右側的語音通話按鈕進行測試",
|
||||||
"welcome_message": "您好,我是您的AI助理,請長按說話按鈕進行對話。"
|
"welcome_message": "您好,我是您的AI助理,請長按說話按鈕進行對話。"
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"translate": {
|
"translate": {
|
||||||
|
|||||||
@ -327,8 +327,8 @@ const getContextMenuItems = (
|
|||||||
t: (key: string) => string,
|
t: (key: string) => string,
|
||||||
selectedQuoteText: string,
|
selectedQuoteText: string,
|
||||||
selectedText: string,
|
selectedText: string,
|
||||||
<<<<<<< HEAD
|
message: Message,
|
||||||
message: Message
|
currentMessage?: Message
|
||||||
): ItemType[] => {
|
): ItemType[] => {
|
||||||
const items: ItemType[] = []
|
const items: ItemType[] = []
|
||||||
|
|
||||||
@ -350,47 +350,31 @@ const getContextMenuItems = (
|
|||||||
EventEmitter.emit(EVENT_NAMES.QUOTE_TEXT, selectedQuoteText)
|
EventEmitter.emit(EVENT_NAMES.QUOTE_TEXT, selectedQuoteText)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
=======
|
|
||||||
currentMessage?: Message
|
// 添加朗读选项
|
||||||
) => [
|
items.push({
|
||||||
{
|
key: 'speak',
|
||||||
key: 'copy',
|
label: '朗读',
|
||||||
label: t('common.copy'),
|
onClick: () => {
|
||||||
onClick: () => {
|
// 从选中的文本开始朗读后面的内容
|
||||||
navigator.clipboard.writeText(selectedText)
|
if (selectedText && currentMessage?.content) {
|
||||||
window.message.success({ content: t('message.copied'), key: 'copy-message' })
|
// 找到选中文本在消息中的位置
|
||||||
}
|
const startIndex = currentMessage.content.indexOf(selectedText)
|
||||||
},
|
if (startIndex !== -1) {
|
||||||
{
|
// 获取选中文本及其后面的所有内容
|
||||||
key: 'quote',
|
const textToSpeak = currentMessage.content.substring(startIndex)
|
||||||
label: t('chat.message.quote'),
|
import('@renderer/services/TTSService').then(({ default: TTSService }) => {
|
||||||
onClick: () => {
|
TTSService.speak(textToSpeak)
|
||||||
EventEmitter.emit(EVENT_NAMES.QUOTE_TEXT, selectedQuoteText)
|
})
|
||||||
}
|
} else {
|
||||||
},
|
// 如果找不到精确位置,则只朗读选中的文本
|
||||||
{
|
import('@renderer/services/TTSService').then(({ default: TTSService }) => {
|
||||||
key: 'speak',
|
TTSService.speak(selectedText)
|
||||||
label: '朗读',
|
})
|
||||||
onClick: () => {
|
}
|
||||||
// 从选中的文本开始朗读后面的内容
|
|
||||||
if (selectedText && currentMessage?.content) {
|
|
||||||
// 找到选中文本在消息中的位置
|
|
||||||
const startIndex = currentMessage.content.indexOf(selectedText)
|
|
||||||
if (startIndex !== -1) {
|
|
||||||
// 获取选中文本及其后面的所有内容
|
|
||||||
const textToSpeak = currentMessage.content.substring(startIndex)
|
|
||||||
import('@renderer/services/TTSService').then(({ default: TTSService }) => {
|
|
||||||
TTSService.speak(textToSpeak)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 如果找不到精确位置,则只朗读选中的文本
|
|
||||||
import('@renderer/services/TTSService').then(({ default: TTSService }) => {
|
|
||||||
TTSService.speak(selectedText)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加复制消息ID选项,但不显示ID
|
// 添加复制消息ID选项,但不显示ID
|
||||||
|
|||||||
@ -222,7 +222,6 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
|
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
|
||||||
{message.mentions?.map((model) => <MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>)}
|
{message.mentions?.map((model) => <MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<<<<<<< HEAD
|
|
||||||
{message.referencedMessages && message.referencedMessages.length > 0 && (
|
{message.referencedMessages && message.referencedMessages.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{message.referencedMessages.map((refMsg, index) => (
|
{message.referencedMessages.map((refMsg, index) => (
|
||||||
@ -317,16 +316,11 @@ const MessageContent: React.FC<Props> = ({ message: _message, model }) => {
|
|||||||
<MessageThought message={message} />
|
<MessageThought message={message} />
|
||||||
<MessageTools message={message} />
|
<MessageTools message={message} />
|
||||||
</div>
|
</div>
|
||||||
<Markdown message={{ ...message, content: processedContent.replace(toolUseRegex, '') }} />
|
|
||||||
=======
|
|
||||||
<MessageThought message={message} />
|
|
||||||
<MessageTools message={message} />
|
|
||||||
{isSegmentedPlayback ? (
|
{isSegmentedPlayback ? (
|
||||||
<TTSHighlightedText text={processedContent.replace(toolUseRegex, '')} />
|
<TTSHighlightedText text={processedContent.replace(toolUseRegex, '')} />
|
||||||
) : (
|
) : (
|
||||||
<Markdown message={{ ...message, content: processedContent.replace(toolUseRegex, '') }} />
|
<Markdown message={{ ...message, content: processedContent.replace(toolUseRegex, '') }} />
|
||||||
)}
|
)}
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
{message.metadata?.generateImage && <MessageImage message={message} />}
|
{message.metadata?.generateImage && <MessageImage message={message} />}
|
||||||
{message.translatedContent && (
|
{message.translatedContent && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
@ -265,12 +265,20 @@ const computeDisplayMessages = (messages: Message[], startIndex: number, display
|
|||||||
|
|
||||||
const userIdSet = new Set() // 用户消息 id 集合
|
const userIdSet = new Set() // 用户消息 id 集合
|
||||||
const assistantIdSet = new Set() // 助手消息 askId 集合
|
const assistantIdSet = new Set() // 助手消息 askId 集合
|
||||||
|
const processedIds = new Set<string>() // 用于跟踪已处理的消息ID
|
||||||
const displayMessages: Message[] = []
|
const displayMessages: Message[] = []
|
||||||
|
|
||||||
// 处理单条消息的函数
|
// 处理单条消息的函数
|
||||||
const processMessage = (message: Message) => {
|
const processMessage = (message: Message) => {
|
||||||
if (!message) return
|
if (!message) return
|
||||||
|
|
||||||
|
// 跳过已处理的消息ID
|
||||||
|
if (processedIds.has(message.id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processedIds.add(message.id) // 标记此消息ID为已处理
|
||||||
|
|
||||||
const idSet = message.role === 'user' ? userIdSet : assistantIdSet
|
const idSet = message.role === 'user' ? userIdSet : assistantIdSet
|
||||||
const messageId = message.role === 'user' ? message.id : message.askId
|
const messageId = message.role === 'user' ? message.id : message.askId
|
||||||
|
|
||||||
@ -279,8 +287,12 @@ const computeDisplayMessages = (messages: Message[], startIndex: number, display
|
|||||||
displayMessages.push(message)
|
displayMessages.push(message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 如果是相同 askId 的助手消息,也要显示
|
|
||||||
displayMessages.push(message)
|
// 如果是相同 askId 的助手消息,检查是否已经有相同ID的消息
|
||||||
|
// 只有在没有相同ID的情况下才添加
|
||||||
|
if (message.role === 'assistant' && !displayMessages.some(m => m.id === message.id)) {
|
||||||
|
displayMessages.push(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遍历消息直到满足显示数量要求
|
// 遍历消息直到满足显示数量要求
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { BookOutlined, FormOutlined, SearchOutlined } from '@ant-design/icons'
|
import { BookOutlined } 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'
|
||||||
@ -11,11 +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 { Button, Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import { LayoutGrid, MessageSquareDiff, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
import { LayoutGrid, MessageSquareDiff, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
@ -64,21 +64,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, activeTopic }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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">
|
||||||
@ -116,9 +102,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, activeTopic }) => {
|
|||||||
<BookOutlined />
|
<BookOutlined />
|
||||||
</NarrowIcon>
|
</NarrowIcon>
|
||||||
</Tooltip>
|
</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()}>
|
||||||
<Search size={18} />
|
<Search size={18} />
|
||||||
@ -189,17 +172,6 @@ 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
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
FolderOutlined,
|
FolderOutlined,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
|
SearchOutlined,
|
||||||
UploadOutlined
|
UploadOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import DragableList from '@renderer/components/DragableList'
|
import DragableList from '@renderer/components/DragableList'
|
||||||
@ -20,6 +21,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
|
|||||||
import { TopicManager } from '@renderer/hooks/useTopic'
|
import { TopicManager } from '@renderer/hooks/useTopic'
|
||||||
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
import { fetchMessagesSummary } from '@renderer/services/ApiService'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
|
import { analyzeAndAddShortMemories } from '@renderer/services/MemoryService'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { RootState } from '@renderer/store'
|
import { RootState } from '@renderer/store'
|
||||||
import { setGenerating } from '@renderer/store/runtime'
|
import { setGenerating } from '@renderer/store/runtime'
|
||||||
@ -238,6 +240,24 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.memory.analyzeConversation') || '分析对话',
|
||||||
|
key: 'analyze-conversation',
|
||||||
|
icon: <SearchOutlined />,
|
||||||
|
async onClick() {
|
||||||
|
try {
|
||||||
|
const result = await analyzeAndAddShortMemories(topic.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') || '分析失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('chat.topics.copy.title'),
|
label: t('chat.topics.copy.title'),
|
||||||
key: 'copy',
|
key: 'copy',
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
import {
|
import { ExperimentOutlined } from '@ant-design/icons'
|
||||||
AppstoreOutlined,
|
|
||||||
CloudOutlined,
|
|
||||||
CodeOutlined,
|
|
||||||
ExperimentOutlined,
|
|
||||||
GlobalOutlined,
|
|
||||||
InfoCircleOutlined,
|
|
||||||
LayoutOutlined,
|
|
||||||
MacCommandOutlined,
|
|
||||||
RocketOutlined,
|
|
||||||
SaveOutlined,
|
|
||||||
SettingOutlined,
|
|
||||||
ThunderboltOutlined
|
|
||||||
} from '@ant-design/icons'
|
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
|
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
|
||||||
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
|
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
|
||||||
@ -160,12 +147,9 @@ const SettingsPage: FC = () => {
|
|||||||
<Route path="model" element={<ModelSettings />} />
|
<Route path="model" element={<ModelSettings />} />
|
||||||
<Route path="web-search" element={<WebSearchSettings />} />
|
<Route path="web-search" element={<WebSearchSettings />} />
|
||||||
<Route path="mcp/*" element={<MCPSettings />} />
|
<Route path="mcp/*" element={<MCPSettings />} />
|
||||||
<<<<<<< HEAD
|
|
||||||
<Route path="memory" element={<MemorySettings />} />
|
<Route path="memory" element={<MemorySettings />} />
|
||||||
<Route path="general" element={<GeneralSettings />} />
|
<Route path="general" element={<GeneralSettings />} />
|
||||||
=======
|
|
||||||
<Route path="general/*" element={<GeneralSettings />} />
|
<Route path="general/*" element={<GeneralSettings />} />
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
<Route path="display" element={<DisplaySettings />} />
|
<Route path="display" element={<DisplaySettings />} />
|
||||||
{showMiniAppSettings && <Route path="miniapps" element={<MiniAppSettings />} />}
|
{showMiniAppSettings && <Route path="miniapps" element={<MiniAppSettings />} />}
|
||||||
<Route path="shortcut" element={<ShortcutSettings />} />
|
<Route path="shortcut" element={<ShortcutSettings />} />
|
||||||
|
|||||||
@ -125,6 +125,14 @@ ${memoriesToCheck}
|
|||||||
|
|
||||||
console.log('[Memory Deduplication] Analysis result:', result)
|
console.log('[Memory Deduplication] Analysis result:', result)
|
||||||
|
|
||||||
|
// 输出更详细的日志信息以便调试
|
||||||
|
console.log('[Memory Deduplication] Attempting to parse result with format:', {
|
||||||
|
hasGroupSection: result.includes('识别出的相似组'),
|
||||||
|
hasIndependentSection: result.includes('独立记忆项'),
|
||||||
|
containsGroupKeyword: result.includes('组'),
|
||||||
|
resultLength: result.length
|
||||||
|
})
|
||||||
|
|
||||||
// 解析结果
|
// 解析结果
|
||||||
const similarGroups: DeduplicationResult['similarGroups'] = []
|
const similarGroups: DeduplicationResult['similarGroups'] = []
|
||||||
const independentMemories: string[] = []
|
const independentMemories: string[] = []
|
||||||
@ -144,38 +152,139 @@ ${memoriesToCheck}
|
|||||||
if (similarGroupsMatch && similarGroupsMatch[1]) {
|
if (similarGroupsMatch && similarGroupsMatch[1]) {
|
||||||
const groupsText = similarGroupsMatch[1].trim()
|
const groupsText = similarGroupsMatch[1].trim()
|
||||||
// 更新正则表达式以匹配新的格式,包括重要性和关键词
|
// 更新正则表达式以匹配新的格式,包括重要性和关键词
|
||||||
const groupRegex =
|
// 输出原始文本以便调试
|
||||||
|
console.log('[Memory Deduplication] Group text to parse:', groupsText)
|
||||||
|
|
||||||
|
// 原始正则表达式
|
||||||
|
const originalGroupRegex =
|
||||||
/-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*-\s*分类:\s*"([^"]+)"\s*(?:-\s*重要性:\s*"([^"]+)")?\s*(?:-\s*关键词:\s*"([^"]+)")?/g
|
/-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*"([^"]+)"\s*-\s*分类:\s*"([^"]+)"\s*(?:-\s*重要性:\s*"([^"]+)")?\s*(?:-\s*关键词:\s*"([^"]+)")?/g
|
||||||
|
|
||||||
let match: RegExpExecArray | null
|
// 新增正则表达式,匹配AI返回的不同格式
|
||||||
while ((match = groupRegex.exec(groupsText)) !== null) {
|
const alternativeGroupRegex = /-\s*组(\d+)?:\s*(?:\*\*)?["\[]?([\d,\s]+)["\]]?(?:\*\*)?\s*-\s*合并建议:\s*(?:\*\*)?["']?([^"'\n-]+)["']?(?:\*\*)?\s*-\s*分类:\s*(?:\*\*)?["']?([^"'\n-]+)["']?(?:\*\*)?/g
|
||||||
const groupId = match[1] || String(similarGroups.length + 1)
|
|
||||||
const memoryIndices = match[2].split(',').map((s: string) => s.trim())
|
// 简化的正则表达式,直接匹配组号和方括号内的数字
|
||||||
const mergedContent = match[3].trim()
|
const simpleGroupRegex = /-\s*组(\d+)?:\s*\[([\d,\s]+)\]\s*-\s*合并建议:\s*(.+?)\s*-\s*分类:\s*(.+?)(?=\s*$|\s*-\s*组|\s*\n)/gm
|
||||||
const category = match[4]?.trim()
|
|
||||||
const importance = match[5] ? parseFloat(match[5].trim()) : undefined
|
// 尝试所有正则表达式
|
||||||
const keywords = match[6]
|
const regexesToTry = [simpleGroupRegex, alternativeGroupRegex, originalGroupRegex]
|
||||||
? match[6]
|
|
||||||
.trim()
|
// 逐个尝试正则表达式
|
||||||
.split(',')
|
for (const regex of regexesToTry) {
|
||||||
.map((k: string) => k.trim())
|
let match: RegExpExecArray | null
|
||||||
: undefined
|
let found = false
|
||||||
|
|
||||||
|
// 重置正则表达式的lastIndex
|
||||||
|
regex.lastIndex = 0
|
||||||
|
|
||||||
|
while ((match = regex.exec(groupsText)) !== null) {
|
||||||
|
found = true
|
||||||
|
const groupId = match[1] || String(similarGroups.length + 1)
|
||||||
|
// 清理引号和方括号
|
||||||
|
const memoryIndicesStr = match[2].replace(/["'\[\]]/g, '')
|
||||||
|
const memoryIndices = memoryIndicesStr.split(',').map((s: string) => s.trim())
|
||||||
|
const mergedContent = match[3].trim().replace(/^["']|["']$/g, '') // 移除首尾的引号
|
||||||
|
const category = match[4]?.trim().replace(/^["']|["']$/g, '') // 移除首尾的引号
|
||||||
|
|
||||||
|
console.log(`[Memory Deduplication] Found group with regex ${regex.toString().substring(0, 30)}...`, {
|
||||||
|
groupId,
|
||||||
|
memoryIndices,
|
||||||
|
mergedContent,
|
||||||
|
category
|
||||||
|
})
|
||||||
|
|
||||||
|
similarGroups.push({
|
||||||
|
groupId,
|
||||||
|
memoryIds: memoryIndices,
|
||||||
|
mergedContent,
|
||||||
|
category: category || '其他'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到了匹配项,就不再尝试其他正则表达式
|
||||||
|
if (found) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旧的解析代码已被上面的新代码替代
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析独立记忆项
|
||||||
|
console.log('[Memory Deduplication] Attempting to parse independent memories')
|
||||||
|
|
||||||
|
// 尝试多种正则表达式匹配独立记忆项
|
||||||
|
const independentRegexes = [
|
||||||
|
/2\.\s*独立记忆项:\s*(?:\*\*)?\s*\[?([\d,\s"]+)\]?(?:\*\*)?/i,
|
||||||
|
/2\.\s*独立记忆项\s*:\s*([\d,\s]+)/i,
|
||||||
|
/独立记忆项\s*:\s*([\d,\s]+)/i
|
||||||
|
]
|
||||||
|
|
||||||
|
let independentFound = false
|
||||||
|
|
||||||
|
for (const regex of independentRegexes) {
|
||||||
|
const independentMatch = result.match(regex)
|
||||||
|
if (independentMatch && independentMatch[1]) {
|
||||||
|
// 处理可能包含引号的情况
|
||||||
|
const cleanedIndependentStr = independentMatch[1].replace(/["'\[\]]/g, '')
|
||||||
|
const items = cleanedIndependentStr.split(',').map((s: string) => s.trim())
|
||||||
|
|
||||||
|
console.log(`[Memory Deduplication] Found independent memories with regex ${regex.toString().substring(0, 30)}...`, items)
|
||||||
|
|
||||||
|
independentMemories.push(...items)
|
||||||
|
independentFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到独立记忆项,尝试直接从结果中提取数字
|
||||||
|
if (!independentFound) {
|
||||||
|
// 尝试直接提取数字
|
||||||
|
const numberMatches = result.match(/\b(\d+)\b/g)
|
||||||
|
if (numberMatches) {
|
||||||
|
// 过滤出不在相似组中的数字
|
||||||
|
const usedIndices = new Set()
|
||||||
|
similarGroups.forEach(group => {
|
||||||
|
group.memoryIds.forEach(id => usedIndices.add(id))
|
||||||
|
})
|
||||||
|
|
||||||
|
const unusedIndices = numberMatches.filter(num => !usedIndices.has(num))
|
||||||
|
if (unusedIndices.length > 0) {
|
||||||
|
console.log('[Memory Deduplication] Extracted independent memories from numbers in result:', unusedIndices)
|
||||||
|
independentMemories.push(...unusedIndices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有解析到相似组和独立记忆项,但结果中包含“组”字样,尝试使用更宽松的正则表达式
|
||||||
|
if (similarGroups.length === 0 && independentMemories.length === 0 && result.includes('组')) {
|
||||||
|
// 尝试使用更宽松的正则表达式提取组信息
|
||||||
|
const looseGroupRegex = /-\s*组\s*(\d+)?\s*:\s*["\[]?\s*([\d,\s"]+)\s*["\]]?\s*-\s*合并建议\s*:\s*["']?([^"'\n-]+)["']?/g
|
||||||
|
|
||||||
|
let looseMatch: RegExpExecArray | null
|
||||||
|
while ((looseMatch = looseGroupRegex.exec(result)) !== null) {
|
||||||
|
const groupId = looseMatch[1] || String(similarGroups.length + 1)
|
||||||
|
// 清理引号和方括号
|
||||||
|
const memoryIndicesStr = looseMatch[2].replace(/["'\[\]]/g, '')
|
||||||
|
const memoryIndices = memoryIndicesStr.split(',').map((s: string) => s.trim())
|
||||||
|
const mergedContent = looseMatch[3].trim()
|
||||||
|
|
||||||
similarGroups.push({
|
similarGroups.push({
|
||||||
groupId,
|
groupId,
|
||||||
memoryIds: memoryIndices,
|
memoryIds: memoryIndices,
|
||||||
mergedContent,
|
mergedContent,
|
||||||
category,
|
category: '其他' // 默认类别
|
||||||
importance,
|
|
||||||
keywords
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析独立记忆项
|
// 如果仍然没有解析到任何内容,尝试直接从原始结果中提取信息
|
||||||
const independentMatch = result.match(/2\.\s*独立记忆项:\s*\[([\d,\s]+)\]/i)
|
if (similarGroups.length === 0 && independentMemories.length === 0) {
|
||||||
if (independentMatch && independentMatch[1]) {
|
console.log('[Memory Deduplication] No groups or independent memories found, attempting direct extraction')
|
||||||
independentMemories.push(...independentMatch[1].split(',').map((s: string) => s.trim()))
|
|
||||||
|
// 尝试提取所有数字作为独立记忆项
|
||||||
|
const allNumbers = result.match(/\b(\d+)\b/g)
|
||||||
|
if (allNumbers && allNumbers.length > 0) {
|
||||||
|
console.log('[Memory Deduplication] Extracted all numbers as independent memories:', allNumbers)
|
||||||
|
independentMemories.push(...allNumbers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Memory Deduplication] Parsed result:', { similarGroups, independentMemories })
|
console.log('[Memory Deduplication] Parsed result:', { similarGroups, independentMemories })
|
||||||
|
|||||||
@ -110,19 +110,39 @@ const analyzeConversation = async (
|
|||||||
let basePrompt =
|
let basePrompt =
|
||||||
customPrompt ||
|
customPrompt ||
|
||||||
`
|
`
|
||||||
请分析对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
|
你是一个专业的对话分析专家,负责从对话中提取关键信息,形成精准的长期记忆。
|
||||||
|
|
||||||
将每条信息分类并按以下格式返回:
|
## 输出格式要求(非常重要):
|
||||||
|
你必须严格按照以下格式输出每条提取的信息:
|
||||||
类别: 信息内容
|
类别: 信息内容
|
||||||
|
|
||||||
类别应该是以下几种之一:
|
有效的类别包括:
|
||||||
- 用户偏好:用户喜好、喜欢的事物、风格等
|
- 用户偏好
|
||||||
- 技术需求:用户的技术相关需求、开发偏好等
|
- 技术需求
|
||||||
- 个人信息:用户的背景、经历等个人信息
|
- 个人信息
|
||||||
- 交互偏好:用户喜欢的交流方式、沟通风格等
|
- 交互偏好
|
||||||
- 其他:不属于以上类别的重要信息
|
- 其他
|
||||||
|
|
||||||
请确保每条信息都是简洁、准确的。如果没有找到重要信息,请返回空字符串。
|
每行必须包含一个类别和一个信息内容,用冒号分隔。
|
||||||
|
不符合此格式的输出将被视为无效。
|
||||||
|
|
||||||
|
示例输出:
|
||||||
|
用户偏好: 用户喜欢简洁直接的代码修改方式。
|
||||||
|
技术需求: 用户需要修复长期记忆分析功能中的问题。
|
||||||
|
个人信息: 用户自称是彭于晏,一位知名演员。
|
||||||
|
交互偏好: 用户倾向于简短直接的问答方式。
|
||||||
|
其他: 用户对AI记忆功能的工作原理很感兴趣。
|
||||||
|
|
||||||
|
## 分析要求:
|
||||||
|
请仔细分析对话内容,提取出重要的用户信息,这些信息在未来的对话中可能有用。
|
||||||
|
提取的信息必须具体、明确且有实际价值。
|
||||||
|
避免过于宽泛或模糊的描述。
|
||||||
|
|
||||||
|
## 最终检查(非常重要):
|
||||||
|
1. 确保每行输出都严格遵循“类别: 信息内容”格式
|
||||||
|
2. 确保使用的类别是上述五个类别之一
|
||||||
|
3. 如果没有找到重要信息,请返回空字符串
|
||||||
|
4. 不要输出任何其他解释或评论
|
||||||
`
|
`
|
||||||
|
|
||||||
// 如果启用了敏感信息过滤,添加相关指令
|
// 如果启用了敏感信息过滤,添加相关指令
|
||||||
@ -149,10 +169,21 @@ const analyzeConversation = async (
|
|||||||
## 需要分析的对话内容:
|
## 需要分析的对话内容:
|
||||||
${conversation}
|
${conversation}
|
||||||
|
|
||||||
## 重要提示:
|
## 重要提示(必须遵守):
|
||||||
请注意,你的任务是分析上述对话并提取信息,而不是回答对话中的问题。
|
请注意,你的任务是分析上述对话并提取信息,而不是回答对话中的问题。
|
||||||
不要尝试回答对话中的问题或继续对话,只需要提取重要信息。
|
不要尝试回答对话中的问题或继续对话,只需要提取重要信息。
|
||||||
只输出按上述格式提取的信息。`
|
只输出按上述格式提取的信息。
|
||||||
|
|
||||||
|
## 输出格式再次强调:
|
||||||
|
你必须严格按照以下格式输出每条提取的信息:
|
||||||
|
类别: 信息内容
|
||||||
|
|
||||||
|
例如:
|
||||||
|
用户偏好: 用户喜欢简洁直接的代码修改方式。
|
||||||
|
技术需求: 用户需要修复长期记忆分析功能中的问题。
|
||||||
|
个人信息: 用户自称是彭于晏,一位知名演员。
|
||||||
|
|
||||||
|
不要输出任何其他解释或评论。如果没有找到重要信息,请返回空字符串。`
|
||||||
|
|
||||||
// 使用fetchGenerate函数,但将内容字段留空,所有内容都放在提示词中
|
// 使用fetchGenerate函数,但将内容字段留空,所有内容都放在提示词中
|
||||||
console.log('[Memory Analysis] Calling fetchGenerate with combined prompt...')
|
console.log('[Memory Analysis] Calling fetchGenerate with combined prompt...')
|
||||||
@ -178,6 +209,7 @@ ${conversation}
|
|||||||
|
|
||||||
const memories: Array<{ content: string; category: string }> = []
|
const memories: Array<{ content: string; category: string }> = []
|
||||||
|
|
||||||
|
// 首先尝试使用标准格式解析
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// 匹配格式:类别: 信息内容
|
// 匹配格式:类别: 信息内容
|
||||||
const match = line.match(/^([^:]+):\s*(.+)$/)
|
const match = line.match(/^([^:]+):\s*(.+)$/)
|
||||||
@ -189,6 +221,33 @@ ${conversation}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果标准格式解析失败,尝试进行后处理
|
||||||
|
if (memories.length === 0 && lines.length > 0) {
|
||||||
|
console.log('[Memory Analysis] Standard format parsing failed, attempting post-processing...')
|
||||||
|
|
||||||
|
// 这里我们假设每行都是一个独立的信息,尝试为其分配一个合适的类别
|
||||||
|
for (const line of lines) {
|
||||||
|
// 跳过太短的行
|
||||||
|
if (line.length < 10) continue
|
||||||
|
|
||||||
|
// 尝试根据内容猜测类别
|
||||||
|
let category = '其他'
|
||||||
|
|
||||||
|
if (line.includes('喜欢') || line.includes('偏好') || line.includes('风格') || line.includes('倾向')) {
|
||||||
|
category = '用户偏好'
|
||||||
|
} else if (line.includes('需要') || line.includes('技术') || line.includes('代码') || line.includes('功能')) {
|
||||||
|
category = '技术需求'
|
||||||
|
} else if (line.includes('自称') || line.includes('身份') || line.includes('背景') || line.includes('经历')) {
|
||||||
|
category = '个人信息'
|
||||||
|
} else if (line.includes('交流') || line.includes('沟通') || line.includes('反馈') || line.includes('询问')) {
|
||||||
|
category = '交互偏好'
|
||||||
|
}
|
||||||
|
|
||||||
|
memories.push({ content: line, category })
|
||||||
|
console.log(`[Memory Analysis] Post-processed memory: ${category}: ${line}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return memories
|
return memories
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to analyze conversation with real AI:', error)
|
console.error('Failed to analyze conversation with real AI:', error)
|
||||||
@ -474,41 +533,45 @@ export const useMemoryService = () => {
|
|||||||
const basePrompt = `
|
const basePrompt = `
|
||||||
你是一个专业的对话分析专家,负责从对话中提取关键信息,形成精准的长期记忆。
|
你是一个专业的对话分析专家,负责从对话中提取关键信息,形成精准的长期记忆。
|
||||||
|
|
||||||
## 重要提示:
|
## 输出格式要求(非常重要):
|
||||||
请注意,你的任务是分析对话并提取信息,而不是回答对话中的问题。不要尝试回答对话中的问题或继续对话,只需要提取重要信息。
|
你必须严格按照以下格式输出每条提取的信息:
|
||||||
|
|
||||||
## 分析要求:
|
|
||||||
请仔细分析对话内容,提取出重要的用户偏好、习惯、需求和背景信息,这些信息在未来的对话中可能有用。
|
|
||||||
|
|
||||||
1. 提取的信息必须是具体、明确且有实际价值的
|
|
||||||
2. 每条信息应该是完整的句子,表达清晰的一个要点
|
|
||||||
3. 避免过于宽泛或模糊的描述
|
|
||||||
4. 确保信息准确反映对话内容,不要过度推断
|
|
||||||
5. 提取的信息应该对未来的对话有帮助
|
|
||||||
|
|
||||||
## 输出格式:
|
|
||||||
将每条信息分类并严格按以下格式返回:
|
|
||||||
类别: 信息内容
|
类别: 信息内容
|
||||||
|
|
||||||
例如:
|
有效的类别包括:
|
||||||
|
- 用户偏好
|
||||||
|
- 技术需求
|
||||||
|
- 个人信息
|
||||||
|
- 交互偏好
|
||||||
|
- 其他
|
||||||
|
|
||||||
|
每行必须包含一个类别和一个信息内容,用冒号分隔。
|
||||||
|
不符合此格式的输出将被视为无效。
|
||||||
|
|
||||||
|
示例输出:
|
||||||
用户偏好: 用户喜欢简洁直接的代码修改方式。
|
用户偏好: 用户喜欢简洁直接的代码修改方式。
|
||||||
技术需求: 用户需要修复长期记忆分析功能中的问题。
|
技术需求: 用户需要修复长期记忆分析功能中的问题。
|
||||||
|
个人信息: 用户自称是彭于晏,一位知名演员。
|
||||||
|
交互偏好: 用户倾向于简短直接的问答方式。
|
||||||
|
其他: 用户对AI记忆功能的工作原理很感兴趣。
|
||||||
|
|
||||||
## 信息类别:
|
## 分析要求:
|
||||||
- 用户偏好:用户喜好、喜欢的事物、风格、审美倾向等
|
请仔细分析对话内容,提取出重要的用户信息,这些信息在未来的对话中可能有用。
|
||||||
- 技术需求:用户的技术相关需求、开发偏好、编程习惯等
|
提取的信息必须具体、明确且有实际价值。
|
||||||
- 个人信息:用户的背景、经历、身份等个人信息
|
避免过于宽泛或模糊的描述。
|
||||||
- 交互偏好:用户喜欢的交流方式、沟通风格、反馈方式等
|
|
||||||
- 其他:不属于以上类别的重要信息
|
|
||||||
|
|
||||||
## 需要分析的对话内容:
|
## 需要分析的对话内容:
|
||||||
${newConversation}
|
${newConversation}
|
||||||
|
|
||||||
## 注意事项:
|
## 重要提示(必须遵守):
|
||||||
- 不要回答对话中的问题
|
请注意,你的任务是分析上述对话并提取信息,而不是回答对话中的问题。
|
||||||
- 不要继续对话或生成新的对话
|
不要尝试回答对话中的问题或继续对话,只需要提取重要信息。
|
||||||
- 只输出按上述格式提取的信息
|
只输出按上述格式提取的信息。
|
||||||
- 如果没有找到重要信息,请返回空字符串
|
|
||||||
|
## 最终检查(非常重要):
|
||||||
|
1. 确保每行输出都严格遵循“类别: 信息内容”格式
|
||||||
|
2. 确保使用的类别是上述五个类别之一
|
||||||
|
3. 如果没有找到重要信息,请返回空字符串
|
||||||
|
4. 不要输出任何其他解释或评论
|
||||||
|
|
||||||
${
|
${
|
||||||
existingMemoriesContent
|
existingMemoriesContent
|
||||||
|
|||||||
@ -185,8 +185,16 @@ export function getAssistantMessage({ assistant, topic }: { assistant: Assistant
|
|||||||
|
|
||||||
export function getGroupedMessages(messages: Message[]): { [key: string]: (Message & { index: number })[] } {
|
export function getGroupedMessages(messages: Message[]): { [key: string]: (Message & { index: number })[] } {
|
||||||
const groups: { [key: string]: (Message & { index: number })[] } = {}
|
const groups: { [key: string]: (Message & { index: number })[] } = {}
|
||||||
|
const processedIds = new Set<string>() // 用于跟踪已处理的消息ID
|
||||||
|
|
||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
|
// 跳过已处理的消息ID
|
||||||
|
if (processedIds.has(message.id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processedIds.add(message.id) // 标记此消息ID为已处理
|
||||||
|
|
||||||
const key = message.askId ? 'assistant' + message.askId : 'user' + message.id
|
const key = message.askId ? 'assistant' + message.askId : 'user' + message.id
|
||||||
if (key && !groups[key]) {
|
if (key && !groups[key]) {
|
||||||
groups[key] = []
|
groups[key] = []
|
||||||
|
|||||||
@ -74,13 +74,24 @@ const messagesSlice = createSlice({
|
|||||||
state.messagesByTopic[topicId] = []
|
state.messagesByTopic[topicId] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前消息列表
|
||||||
|
const currentMessages = state.messagesByTopic[topicId]
|
||||||
|
|
||||||
if (Array.isArray(messages)) {
|
if (Array.isArray(messages)) {
|
||||||
// 为了兼容多模型新发消息,一次性添加多个助手消息
|
// 为了兼容多模型新发消息,一次性添加多个助手消息
|
||||||
// 不是什么好主意,不符合语义
|
// 不是什么好主意,不符合语义
|
||||||
state.messagesByTopic[topicId].push(...messages)
|
// 检查每条消息是否已存在,避免重复添加
|
||||||
|
const messagesToAdd = messages.filter(msg =>
|
||||||
|
!currentMessages.some(existing => existing.id === msg.id)
|
||||||
|
)
|
||||||
|
if (messagesToAdd.length > 0) {
|
||||||
|
state.messagesByTopic[topicId].push(...messagesToAdd)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 添加单条消息
|
// 添加单条消息,先检查是否已存在
|
||||||
state.messagesByTopic[topicId].push(messages)
|
if (!currentMessages.some(existing => existing.id === messages.id)) {
|
||||||
|
state.messagesByTopic[topicId].push(messages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appendMessage: (
|
appendMessage: (
|
||||||
@ -96,15 +107,23 @@ const messagesSlice = createSlice({
|
|||||||
// 确保消息数组存在并且拿到引用
|
// 确保消息数组存在并且拿到引用
|
||||||
const messagesList = state.messagesByTopic[topicId]
|
const messagesList = state.messagesByTopic[topicId]
|
||||||
|
|
||||||
// 要插入的消息
|
// 要插入的消息,先过滤掉已存在的消息
|
||||||
const messagesToInsert = Array.isArray(messages) ? messages : [messages]
|
const messagesToInsert = Array.isArray(messages) ? messages : [messages]
|
||||||
|
const uniqueMessagesToInsert = messagesToInsert.filter(msg =>
|
||||||
|
!messagesList.some(existing => existing.id === msg.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果没有新消息需要插入,直接返回
|
||||||
|
if (uniqueMessagesToInsert.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (position !== undefined && position >= 0 && position <= messagesList.length) {
|
if (position !== undefined && position >= 0 && position <= messagesList.length) {
|
||||||
// 如果指定了位置,在特定位置插入消息
|
// 如果指定了位置,在特定位置插入消息
|
||||||
messagesList.splice(position, 0, ...messagesToInsert)
|
messagesList.splice(position, 0, ...uniqueMessagesToInsert)
|
||||||
} else {
|
} else {
|
||||||
// 否则默认添加到末尾
|
// 否则默认添加到末尾
|
||||||
messagesList.push(...messagesToInsert)
|
messagesList.push(...uniqueMessagesToInsert)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateMessage: (
|
updateMessage: (
|
||||||
@ -158,16 +177,25 @@ const messagesSlice = createSlice({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 尝试找到现有消息
|
// 尝试找到现有消息
|
||||||
const existingMessage = state.messagesByTopic[topicId].find(
|
const existingMessageIndex = state.messagesByTopic[topicId].findIndex(
|
||||||
(m) => m.role === 'assistant' && m.id === streamMessage.id
|
(m) => m.role === 'assistant' && m.id === streamMessage.id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (existingMessage) {
|
if (existingMessageIndex !== -1) {
|
||||||
// 更新
|
// 更新现有消息
|
||||||
Object.assign(existingMessage, streamMessage)
|
Object.assign(state.messagesByTopic[topicId][existingMessageIndex], streamMessage)
|
||||||
} else {
|
} else {
|
||||||
// 添加新消息
|
// 检查是否有重复的消息(相同的askId和内容)
|
||||||
state.messagesByTopic[topicId].push(streamMessage)
|
const duplicateMessage = state.messagesByTopic[topicId].find(
|
||||||
|
(m) => m.role === 'assistant' &&
|
||||||
|
m.askId === streamMessage.askId &&
|
||||||
|
m.content === streamMessage.content
|
||||||
|
)
|
||||||
|
|
||||||
|
// 只有在没有重复消息的情况下才添加新消息
|
||||||
|
if (!duplicateMessage) {
|
||||||
|
state.messagesByTopic[topicId].push(streamMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除流状态
|
// 删除流状态
|
||||||
|
|||||||
@ -4126,7 +4126,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@vitejs/plugin-react@npm:^4.2.1":
|
"@vitejs/plugin-react@npm:^4.3.4":
|
||||||
version: 4.3.4
|
version: 4.3.4
|
||||||
resolution: "@vitejs/plugin-react@npm:4.3.4"
|
resolution: "@vitejs/plugin-react@npm:4.3.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4233,7 +4233,7 @@ __metadata:
|
|||||||
"@types/react-dom": "npm:^19.0.4"
|
"@types/react-dom": "npm:^19.0.4"
|
||||||
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
||||||
"@types/tinycolor2": "npm:^1"
|
"@types/tinycolor2": "npm:^1"
|
||||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
"@vitejs/plugin-react": "npm:^4.3.4"
|
||||||
"@xyflow/react": "npm:^12.4.4"
|
"@xyflow/react": "npm:^12.4.4"
|
||||||
adm-zip: "npm:^0.5.16"
|
adm-zip: "npm:^0.5.16"
|
||||||
analytics: "npm:^0.8.16"
|
analytics: "npm:^0.8.16"
|
||||||
@ -15620,7 +15620,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
"rw@npm:1":
|
"rw@npm:1":
|
||||||
version: 1.3.3
|
version: 1.3.3
|
||||||
resolution: "rw@npm:1.3.3"
|
resolution: "rw@npm:1.3.3"
|
||||||
@ -15628,10 +15627,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
|
|
||||||
=======
|
|
||||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
|
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
|
||||||
>>>>>>> origin/1600822305-patch-2
|
|
||||||
version: 5.2.1
|
version: 5.2.1
|
||||||
resolution: "safe-buffer@npm:5.2.1"
|
resolution: "safe-buffer@npm:5.2.1"
|
||||||
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
|
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user