Version: 0.8.3.dev.260328

后端:
1.彻底删除原agent文件夹,并将现agent2文件夹全量重命名为agent(包括全部涉及到的文件以及文档、注释),迁移工作完美结束
2.修复了重试消息的相关逻辑问题

前端:
1.改善了一些交互体验,修复了一些bug,现在只剩少的功能了,现存的bug基本都修复完毕

全仓库:
1.更新了决策记录和README文档
This commit is contained in:
Losita
2026-03-28 18:00:31 +08:00
parent 5fc9548420
commit 468367d617
108 changed files with 1910 additions and 17173 deletions

View File

@@ -677,6 +677,27 @@ function resolveVisibleUserMessageBeforeAssistant(messageId: string) {
return null
}
function findMessageIndexInList(messages: AssistantMessage[], messageId: string) {
return messages.findIndex((message) => message.id === messageId)
}
function resolveUserMessageBeforeAssistantInBucket(conversationId: string, assistantMessageId: string) {
const bucket = conversationMessagesMap[conversationId] ?? []
const index = findMessageIndexInList(bucket, assistantMessageId)
if (index <= 0) {
return null
}
for (let current = index - 1; current >= 0; current -= 1) {
const candidate = bucket[current]
if (candidate?.role === 'user') {
return candidate
}
}
return null
}
function isLocalEphemeralMessageId(id: string) {
return /^(user|assistant|system)-\d{13}-[a-z0-9]+$/i.test(id)
}
@@ -697,6 +718,81 @@ function resolvePersistedMessageId(message: AssistantMessage | null) {
return message.id
}
function resolveBestMatchedMessageFromBucket(conversationId: string, targetMessage: AssistantMessage) {
const bucket = conversationMessagesMap[conversationId] ?? []
const directMatchedMessage = bucket.find((message) => message.id === targetMessage.id)
if (directMatchedMessage) {
return directMatchedMessage
}
const targetTimestamp = resolveMessageTimestamp(targetMessage)
const logicalMatchedMessages = bucket
.filter((message) => isSameLogicalMessage(message, targetMessage))
.sort((left, right) => {
// 1. 优先命中已经拿到后端稳定主键的消息,避免继续引用本地占位态。
// 2. 若候选状态一致,则优先选择时间更接近原消息的那条。
// 3. 时间也一致时再按较新的记录兜底,降低重复文案时误命中旧消息的概率。
const persistedScoreDiff =
Number(!isLocalEphemeralMessageId(right.id)) - Number(!isLocalEphemeralMessageId(left.id))
if (persistedScoreDiff !== 0) {
return persistedScoreDiff
}
const leftGap = Math.abs(resolveMessageTimestamp(left) - targetTimestamp)
const rightGap = Math.abs(resolveMessageTimestamp(right) - targetTimestamp)
if (leftGap !== rightGap) {
return leftGap - rightGap
}
return resolveMessageTimestamp(right) - resolveMessageTimestamp(left)
})
return logicalMatchedMessages[0] ?? null
}
async function resolveRetrySourceMessages(
conversationId: string,
sourceUserMessage: AssistantMessage,
sourceAssistantMessage: AssistantMessage,
) {
let resolvedUserMessage: AssistantMessage | null = sourceUserMessage
let resolvedAssistantMessage: AssistantMessage | null = sourceAssistantMessage
let persistedUserMessageId = resolvePersistedMessageId(resolvedUserMessage)
let persistedAssistantMessageId = resolvePersistedMessageId(resolvedAssistantMessage)
if (persistedUserMessageId && persistedAssistantMessageId) {
return {
sourceUserMessage: resolvedUserMessage,
sourceAssistantMessage: resolvedAssistantMessage,
persistedUserMessageId,
persistedAssistantMessageId,
}
}
// 1. 若当前点击时仍是本地占位消息,先静默拉一次权威历史,尽量把真实 ID 补回来。
// 2. 这里复用现有 history 接口即可,避免为了一次重试再新增额外查询接口。
// 3. 若静默刷新后依然拿不到稳定 ID则说明消息大概率仍处于异步持久化窗口期。
await loadConversationMessages(conversationId, true)
resolvedAssistantMessage =
resolveBestMatchedMessageFromBucket(conversationId, sourceAssistantMessage) ?? sourceAssistantMessage
resolvedUserMessage =
resolveUserMessageBeforeAssistantInBucket(conversationId, resolvedAssistantMessage.id) ??
resolveBestMatchedMessageFromBucket(conversationId, sourceUserMessage) ??
sourceUserMessage
persistedUserMessageId = resolvePersistedMessageId(resolvedUserMessage)
persistedAssistantMessageId = resolvePersistedMessageId(resolvedAssistantMessage)
return {
sourceUserMessage: resolvedUserMessage,
sourceAssistantMessage: resolvedAssistantMessage,
persistedUserMessageId,
persistedAssistantMessageId,
}
}
function createRetryGroupId() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `retry-${crypto.randomUUID()}`
@@ -1407,32 +1503,36 @@ async function regenerateAssistantMessage(message: AssistantMessage) {
}
const sourceUserMessage = resolveVisibleUserMessageBeforeAssistant(message.id)
const text = sourceUserMessage?.content.trim() || ''
const conversationId = selectedConversationId.value
const persistedUserMessageId = resolvePersistedMessageId(sourceUserMessage)
const persistedAssistantMessageId = resolvePersistedMessageId(message)
if (!text || !conversationId || !sourceUserMessage) {
if (!conversationId || !sourceUserMessage) {
ElMessage.warning('没有找到可用于重试的用户消息')
return
}
if (!persistedUserMessageId) {
ElMessage.info('当前消息仍在本地态,稍后刷新完成后再试重试')
const retrySource = await resolveRetrySourceMessages(conversationId, sourceUserMessage, message)
const text = retrySource.sourceUserMessage?.content.trim() || sourceUserMessage.content.trim()
if (!text) {
ElMessage.warning('没有找到可用于重试的用户消息')
return
}
if (!persistedAssistantMessageId) {
ElMessage.info('当前回复仍在本地态,稍后刷新完成后再试重试')
if (!retrySource.persistedUserMessageId || !retrySource.persistedAssistantMessageId) {
ElMessage.info('消息正在处理,请稍后再重试,或者直接复制消息重新发送')
return
}
chatLoading.value = true
cancelEditUserMessage()
const retryGroup = resolveRetryPageGroup(message)
const retryGroup = resolveRetryPageGroup(retrySource.sourceAssistantMessage)
const retryGroupId = retryGroup?.groupId || createRetryGroupId()
const nextRetryIndex = (retryGroup?.total ?? 1) + 1
applyRetryGroupToExistingMessages(retryGroupId, nextRetryIndex, sourceUserMessage.id, message.id)
applyRetryGroupToExistingMessages(
retryGroupId,
nextRetryIndex,
retrySource.sourceUserMessage.id,
retrySource.sourceAssistantMessage.id,
)
const now = new Date().toISOString()
appendConversationMessage(conversationId, {
@@ -1464,8 +1564,8 @@ async function regenerateAssistantMessage(message: AssistantMessage) {
try {
const actualConversationId = await streamAssistantReply(conversationId, text, retryAssistantMessage, now, true, {
retryGroupId,
retryFromUserMessageId: persistedUserMessageId,
retryFromAssistantMessageId: persistedAssistantMessageId,
retryFromUserMessageId: retrySource.persistedUserMessageId,
retryFromAssistantMessageId: retrySource.persistedAssistantMessageId,
})
await loadConversationMessages(actualConversationId, true)
} catch (error) {
@@ -3169,4 +3269,3 @@ onBeforeUnmount(() => {
background: rgba(51, 95, 194, 0.16);
}
</style>