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