feat: 对话缓存机制
This commit is contained in:
@@ -5,6 +5,7 @@ import { ref, computed } from 'vue'
|
||||
import { removeLocal, setLocal } from '@/utils/local'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
|
||||
|
||||
// Agent 项目初始数据 store
|
||||
type InitialProjectData = {
|
||||
text: string
|
||||
@@ -12,13 +13,13 @@ type InitialProjectData = {
|
||||
type: string
|
||||
area: string
|
||||
style: string
|
||||
useReport: boolean
|
||||
needSuggestion: boolean
|
||||
useReport:boolean
|
||||
needSuggestion:boolean
|
||||
quoteList: Array<string>
|
||||
tempImages: any[]
|
||||
}
|
||||
export const useAgentStore = defineStore('agent', () => {
|
||||
const initialProjectData = ref<InitialProjectData | null>(null)
|
||||
const initialProjectData = ref<InitialProjectData | null>(null)
|
||||
|
||||
// 保存项目初始数据
|
||||
const setInitialProjectData = (data: InitialProjectData) => {
|
||||
@@ -33,94 +34,10 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
initialProjectData.value = null
|
||||
}
|
||||
|
||||
// 会话缓存管理,最多保存 10 条会话
|
||||
const conversationCache = new Map<string, any>()
|
||||
const MAX_CACHE_ENTRIES = 10
|
||||
const SESSION_KEY_PREFIX = 'agent_session_'
|
||||
|
||||
const pruneCache = () => {
|
||||
const keys = Array.from(conversationCache.keys())
|
||||
const total = conversationCache.size
|
||||
if (total <= MAX_CACHE_ENTRIES) return
|
||||
|
||||
for (let i = 0; i < total - MAX_CACHE_ENTRIES; i++) {
|
||||
conversationCache.delete(keys[i])
|
||||
sessionStorage.removeItem(SESSION_KEY_PREFIX + keys[i])
|
||||
}
|
||||
}
|
||||
|
||||
const saveCacheConversation = (projectId: string, data: any) => {
|
||||
if (!projectId) return
|
||||
|
||||
const finalData = {
|
||||
...data,
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
|
||||
if (conversationCache.has(projectId)) {
|
||||
conversationCache.delete(projectId)
|
||||
}
|
||||
|
||||
conversationCache.set(projectId, finalData)
|
||||
try {
|
||||
sessionStorage.setItem(SESSION_KEY_PREFIX + projectId, JSON.stringify(finalData))
|
||||
} catch (e) {
|
||||
console.warn('saveCacheConversation sessionStorage failed', e)
|
||||
}
|
||||
|
||||
pruneCache()
|
||||
}
|
||||
|
||||
const getCacheConversation = (projectId: string) => {
|
||||
if (!projectId) return null
|
||||
|
||||
let data = conversationCache.get(projectId)
|
||||
if (data) return data
|
||||
|
||||
try {
|
||||
const raw = sessionStorage.getItem(SESSION_KEY_PREFIX + projectId)
|
||||
if (!raw) return null
|
||||
data = JSON.parse(raw)
|
||||
conversationCache.set(projectId, data)
|
||||
return data
|
||||
} catch (e) {
|
||||
console.warn('getCacheConversation failed', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const clearAllCacheConversations = () => {
|
||||
conversationCache.value.clear()
|
||||
for (const key in window.sessionStorage) {
|
||||
if (key.startsWith(SESSION_KEY_PREFIX)) {
|
||||
sessionStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeCacheConversation = (projectId: string) => {
|
||||
if (!projectId) return
|
||||
conversationCache.value.delete(projectId)
|
||||
sessionStorage.removeItem(SESSION_KEY_PREFIX + projectId)
|
||||
}
|
||||
|
||||
const getCacheStats = () => {
|
||||
return {
|
||||
total: conversationCache.value.size,
|
||||
keys: Array.from(conversationCache.value.keys())
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initialProjectData,
|
||||
setInitialProjectData,
|
||||
getInitialProjectData,
|
||||
clearInitialProjectData,
|
||||
conversationCache,
|
||||
saveCacheConversation,
|
||||
getCacheConversation,
|
||||
removeCacheConversation,
|
||||
clearAllCacheConversations,
|
||||
getCacheStats
|
||||
clearInitialProjectData
|
||||
}
|
||||
})
|
||||
|
||||
@@ -155,13 +155,13 @@ function addPending(config: any) {
|
||||
* @param {*} config
|
||||
*/
|
||||
function removePending(config: any) {
|
||||
const pendingKey = getPendingKey(config)
|
||||
if (pendingMap.has(pendingKey)) {
|
||||
const cancelToken = pendingMap.get(pendingKey)
|
||||
cancelToken(pendingKey)
|
||||
pendingMap.delete(pendingKey)
|
||||
return true
|
||||
}
|
||||
// const pendingKey = getPendingKey(config)
|
||||
// if (pendingMap.has(pendingKey)) {
|
||||
// const cancelToken = pendingMap.get(pendingKey)
|
||||
// cancelToken(pendingKey)
|
||||
// pendingMap.delete(pendingKey)
|
||||
// return true
|
||||
// }
|
||||
}
|
||||
// ----------------------------------loading的函数-------------------------------
|
||||
const LoadingInstance: { _count: number } = {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!-- <SvgIcon name="equal" color="#0d0d0d" size="24" /> -->
|
||||
</div>
|
||||
<div class="agent-body flex-1 flex flex-col">
|
||||
<List ref="listRef" :message-list="messageList" @regenerate="handleRegenerate" />
|
||||
<List ref="listRef" :message-list="messageList" />
|
||||
<Input
|
||||
ref="inputRef"
|
||||
is-agent-mode
|
||||
@@ -80,55 +80,7 @@
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const saveSession = (projectId: string) => {
|
||||
if (!projectId) return
|
||||
agentStore.saveCacheConversation(projectId, {
|
||||
messageList: messageList.value,
|
||||
sketchList: sketchList.value,
|
||||
params: {
|
||||
...params,
|
||||
projectID: projectId
|
||||
},
|
||||
isGenerating: isGenerating.value,
|
||||
isPaused: isPaused.value
|
||||
})
|
||||
}
|
||||
|
||||
const restoreSession = (projectId: string) => {
|
||||
if (!projectId) return false
|
||||
const payload = agentStore.getCacheConversation(projectId)
|
||||
if (!payload) return false
|
||||
|
||||
messageList.value = payload.messageList || []
|
||||
sketchList.value = payload.sketchList || []
|
||||
if (payload.params) {
|
||||
params.projectID = payload.params.projectID || projectId
|
||||
params.message = payload.params.message || ''
|
||||
params.versionID = payload.params.versionID || ''
|
||||
params.configParams = payload.params.configParams || params.configParams
|
||||
params.needSuggestion = payload.params.needSuggestion || false
|
||||
params.useReport = payload.params.useReport || false
|
||||
params.imageUrlList = payload.params.imageUrlList || []
|
||||
params.quotaUrl = payload.params.quotaUrl || []
|
||||
}
|
||||
isGenerating.value = payload.isGenerating || false
|
||||
isPaused.value = payload.isPaused || false
|
||||
return true
|
||||
}
|
||||
|
||||
const clearSession = (projectId: string) => {
|
||||
if (!projectId) return
|
||||
agentStore.removeCacheConversation(projectId)
|
||||
}
|
||||
|
||||
const handleReset = (force = false) => {
|
||||
if (!force && params.projectID) {
|
||||
saveSession(params.projectID)
|
||||
}
|
||||
if (force && params.projectID) {
|
||||
clearSession(params.projectID)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
messageList.value = []
|
||||
sketchList.value = []
|
||||
params.versionID = ''
|
||||
@@ -142,7 +94,6 @@
|
||||
style: ''
|
||||
}
|
||||
isGenerating.value = false
|
||||
isPaused.value = false
|
||||
}
|
||||
|
||||
// 每次请求时创建新的 AbortController
|
||||
@@ -158,10 +109,7 @@
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (params.projectID) {
|
||||
saveSession(params.projectID)
|
||||
}
|
||||
abort?.abort()
|
||||
// abort?.abort()
|
||||
MyEvent.remove('resetAgent', handleReset)
|
||||
})
|
||||
|
||||
@@ -410,7 +358,14 @@
|
||||
}
|
||||
if (jsonData.title) {
|
||||
emits('setTitle', jsonData.title)
|
||||
MyEvent.emit('newTitle', jsonData.title)
|
||||
console.log('发送title', {
|
||||
title: jsonData.title,
|
||||
id: params.projectID
|
||||
})
|
||||
MyEvent.emit('newTitle', {
|
||||
title: jsonData.title,
|
||||
id: params.projectID
|
||||
})
|
||||
}
|
||||
|
||||
if (hasSketch) {
|
||||
@@ -505,39 +460,36 @@
|
||||
isPaused.value = true
|
||||
isGenerating.value = false
|
||||
abort?.abort()
|
||||
if (params.projectID) {
|
||||
saveSession(params.projectID)
|
||||
}
|
||||
MyEvent.emit('stopChat')
|
||||
}
|
||||
|
||||
const handleRegenerate = async (aiMessage: any) => {
|
||||
// 找到当前 AI 消息在列表中的索引
|
||||
const aiIndex = messageList.value.findIndex((msg) => msg.id === aiMessage.id)
|
||||
if (aiIndex === -1) return
|
||||
// const handleRegenerate = async (aiMessage: any) => {
|
||||
// // 找到当前 AI 消息在列表中的索引
|
||||
// const aiIndex = messageList.value.findIndex((msg) => msg.id === aiMessage.id)
|
||||
// if (aiIndex === -1) return
|
||||
|
||||
// 找到对应的用户消息(AI 消息前面的最近一条用户消息)
|
||||
let userMessage = null
|
||||
for (let i = aiIndex - 1; i >= 0; i--) {
|
||||
if (messageList.value[i].isUser) {
|
||||
userMessage = messageList.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!userMessage) return
|
||||
// // 找到对应的用户消息(AI 消息前面的最近一条用户消息)
|
||||
// let userMessage = null
|
||||
// for (let i = aiIndex - 1; i >= 0; i--) {
|
||||
// if (messageList.value[i].isUser) {
|
||||
// userMessage = messageList.value[i]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if (!userMessage) return
|
||||
|
||||
// 删除当前的 AI 回复消息
|
||||
messageList.value.splice(aiIndex, 1)
|
||||
// // 删除当前的 AI 回复消息
|
||||
// messageList.value.splice(aiIndex, 1)
|
||||
|
||||
// 重新调用 API(跳过用户消息添加,因为用户消息已存在)
|
||||
await handleSendMessage(
|
||||
{
|
||||
text: userMessage.text,
|
||||
images: userMessage.images || []
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
// // 重新调用 API(跳过用户消息添加,因为用户消息已存在)
|
||||
// await handleSendMessage(
|
||||
// {
|
||||
// text: userMessage.text,
|
||||
// images: userMessage.images || []
|
||||
// },
|
||||
// true
|
||||
// )
|
||||
// }
|
||||
|
||||
// 处理对话列表,将连续的 assistant 消息合并为一条
|
||||
const processDialogue = (dialogue, startIndex, existingImgList, sessionId) => {
|
||||
@@ -598,7 +550,7 @@
|
||||
thinkingText: combinedThinkingText,
|
||||
text: combinedContent,
|
||||
image_url: combinedImageUrl,
|
||||
webAddress: webAddress ? JSON.parse(webAddress) : null,
|
||||
webAddress: !!webAddress ? JSON.parse(webAddress) : null,
|
||||
isUser: false,
|
||||
id: result.length + 1,
|
||||
sessionId: sessionId
|
||||
@@ -674,7 +626,7 @@
|
||||
item.text += `<slot slot-name="sketch"></slot>`
|
||||
}
|
||||
})
|
||||
console.log('ancestorslist', ancestorsList)
|
||||
// console.log('ancestorslist', ancestorsList)
|
||||
messageList.value = [...ancestorsList]
|
||||
params.versionID = current?.id
|
||||
sketchList.value = imgList
|
||||
@@ -682,10 +634,7 @@
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
setChatInfo,
|
||||
saveSession,
|
||||
restoreSession,
|
||||
clearSession
|
||||
setChatInfo
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
<div class="btn" @click="versionTreeData.drawer = true">Version Tree</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<Agent
|
||||
ref="agentRef"
|
||||
:title="agentTitle"
|
||||
@update:sketchList="updateSketchList"
|
||||
@setTitle="handleSetTitle"
|
||||
/>
|
||||
<KeepAlive :max="10">
|
||||
<Agent
|
||||
:key="proJectId"
|
||||
ref="agentRef"
|
||||
:title="agentTitle"
|
||||
@update:sketchList="updateSketchList"
|
||||
@setTitle="handleSetTitle"
|
||||
/>
|
||||
</KeepAlive>
|
||||
<div class="preview-wrapper">
|
||||
<Preview
|
||||
ref="previewRef"
|
||||
@@ -131,31 +134,18 @@
|
||||
|
||||
watch(
|
||||
() => proJectId.value,
|
||||
async (newVal, oldVal) => {
|
||||
if (oldVal && agentRef.value && typeof agentRef.value.saveSession === 'function') {
|
||||
agentRef.value.saveSession(oldVal as string)
|
||||
}
|
||||
|
||||
(newVal, oldVal) => {
|
||||
projectStore.clearProject()
|
||||
|
||||
if (newVal) {
|
||||
let restored = false
|
||||
if (agentRef.value && typeof agentRef.value.restoreSession === 'function') {
|
||||
restored = await agentRef.value.restoreSession(newVal as string)
|
||||
}
|
||||
|
||||
if (!restored) {
|
||||
handleGetProjectInfoAndHistory()
|
||||
}
|
||||
|
||||
MyEvent.emit('projectChange')
|
||||
} else {
|
||||
handleGetProjectInfoAndHistory()
|
||||
MyEvent.emit('projectChange')
|
||||
// MyEvent.emit('resetAgent')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
console.log('11111agentindex')
|
||||
MyEvent.add('openReport', handleOpenReport)
|
||||
MyEvent.add('openUrls', handleOpenUrls)
|
||||
MyEvent.add('openSketch', handleOpenSketch)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="right-main">
|
||||
<top-nav />
|
||||
<div class="bottom-view"><router-view></router-view></div>
|
||||
</div>
|
||||
</div>¬
|
||||
</div>
|
||||
<setting />
|
||||
<flow-canvas ref="flowCanvasRef" />
|
||||
|
||||
@@ -207,10 +207,10 @@
|
||||
MyEvent.add('updateProjectList', GetProjectList)
|
||||
GetProjectList()
|
||||
|
||||
const replaceTitle = (title: string) => {
|
||||
const replaceTitle = (data: { title: string; id: string | number }) => {
|
||||
list.value.forEach((item: any) => {
|
||||
if (String(item.id) === String(projectStore.state.id)) {
|
||||
item.name = title
|
||||
if (String(item.id) === String(data.id)) {
|
||||
item.name = data.title
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user