diff --git a/src/main/presenter/sqlitePresenter/tables/messages.ts b/src/main/presenter/sqlitePresenter/tables/messages.ts index 6975a9d8d..69120f20a 100644 --- a/src/main/presenter/sqlitePresenter/tables/messages.ts +++ b/src/main/presenter/sqlitePresenter/tables/messages.ts @@ -293,8 +293,9 @@ export class MessagesTable extends BaseTable { FROM messages WHERE conversation_id = ? AND parent_id = ? + AND role = 'assistant' AND is_variant = 0 - ORDER BY created_at ASC + ORDER BY created_at DESC LIMIT 1 ` ) diff --git a/src/renderer/src/components/message/MessageItemAssistant.vue b/src/renderer/src/components/message/MessageItemAssistant.vue index 949cd2675..111af15f6 100644 --- a/src/renderer/src/components/message/MessageItemAssistant.vue +++ b/src/renderer/src/components/message/MessageItemAssistant.vue @@ -177,21 +177,12 @@ const emit = defineEmits<{ // 获取当前会话ID const currentThreadId = computed(() => chatStore.getActiveThreadId() || '') -// 将 currentVariantIndex 从 ref 改造为 computed 属性 -// 这确保了其值总是与 Pinia store 中的状态同步,从根本上消除了竞态条件。 +// currentVariantIndex: 0 = 主消息, 1-N = 对应的变体索引 const currentVariantIndex = computed(() => { - const selectedVariantId = chatStore.selectedVariantsMap.get(props.message.id) + const selectedVariantId = chatStore.selectedVariantsMap[props.message.id] + if (!selectedVariantId) return 0 - // 如果 store 中没有记录,则显示主消息 (索引 0) - if (!selectedVariantId) { - return 0 - } - - // 在所有变体中查找已选择的 ID const variantIndex = allVariants.value.findIndex((v) => v.id === selectedVariantId) - - // 如果找到了,返回其索引 + 1 (因为索引 0 是主消息) - // 如果没找到 (数据过时或已删除),则安全地回退到主消息 return variantIndex !== -1 ? variantIndex + 1 : 0 }) @@ -205,19 +196,26 @@ const currentMessage = computed(() => { return variant || props.message }) -// 计算当前消息的所有变体(包括缓存中的) +// 计算当前消息的所有变体(包括缓存中的,过滤掉主消息本身) const allVariants = computed(() => { const messageVariants = props.message.variants || [] - const combinedVariants = messageVariants.map((variant) => { - const cachedVariant = Array.from(chatStore.getGeneratingMessagesCache().values()).find( - (cached) => { - const msg = cached.message as AssistantMessage - return msg.is_variant && msg.id === variant.id - } - ) - return cachedVariant ? cachedVariant.message : variant + const variantsById = new Map() + + // 只添加真正的变体(is_variant !== 0),过滤掉主消息本身 + messageVariants.forEach((variant) => { + if (variant.is_variant !== 0) { + variantsById.set(variant.id, variant as AssistantMessage) + } }) - return combinedVariants + + for (const [, cached] of chatStore.getGeneratingMessagesCache().entries()) { + const msg = cached.message + if (msg.role === 'assistant' && msg.is_variant && msg.parentId === props.message.id) { + variantsById.set(msg.id, msg as AssistantMessage) + } + } + + return Array.from(variantsById.values()) }) // 计算变体总数 @@ -240,14 +238,12 @@ watch( // 仅当新变体被添加时触发 // 并且当前会话不是正在生成中的消息,避免在生成过程中频繁切换 if (newLength > oldLength && !chatStore.generatingThreadIds.has(currentThreadId.value)) { - const newVariantIndex = newLength // 新变体的索引 - const mainMessageId = props.message.id - // newVariantIndex 此时至少为 1 - const selectedVariant = allVariants.value[newVariantIndex - 1] + // 获取最后一个变体(数组最后一个元素) + const lastVariant = allVariants.value[newLength - 1] - // 只有当 selectedVariant 存在时才调用 updateSelectedVariant,确保是有效的变体 - chatStore.updateSelectedVariant(mainMessageId, selectedVariant ? selectedVariant.id : null) + // 只有当 lastVariant 存在时才调用 updateSelectedVariant,确保是有效的变体 + chatStore.updateSelectedVariant(mainMessageId, lastVariant ? lastVariant.id : null) } } ) @@ -324,30 +320,18 @@ const handleAction = (action: HandleActionType) => { .trim() ) } else if (action === 'prev' || action === 'next') { - // 修改 prev/next 逻辑以遵循单向数据流 - let newIndex = currentVariantIndex.value // 获取当前计算出的索引 + let newIndex = currentVariantIndex.value - switch (action) { - case 'prev': - if (newIndex > 0) newIndex-- - break - case 'next': - if (newIndex < totalVariants.value - 1) newIndex++ - break + if (action === 'prev' && newIndex > 0) { + newIndex-- + } else if (action === 'next' && newIndex < totalVariants.value - 1) { + newIndex++ } - // 如果计算出的新索引与当前索引相同,则不执行任何操作 if (newIndex === currentVariantIndex.value) return - const mainMessageId = props.message.id - // 如果 newIndex 是 0,则 selectedVariant 为 null,表示选择主消息 - const selectedVariant = newIndex > 0 ? allVariants.value[newIndex - 1] : null - const selectedVariantId = selectedVariant ? selectedVariant.id : null - - // 不再直接修改本地 state,而是调用 store 的 action 来更新全局状态 - // store 的更新会通过 computed 属性自动反馈到 UI - chatStore.updateSelectedVariant(mainMessageId, selectedVariantId) - + const selectedVariantId = newIndex > 0 ? allVariants.value[newIndex - 1]?.id : null + chatStore.updateSelectedVariant(props.message.id, selectedVariantId) emit('variantChanged', props.message.id) } else if (action === 'copyImage') { // 使用原始消息的ID,因为DOM中的data-message-id使用的是message.id diff --git a/src/renderer/src/components/message/MessageList.vue b/src/renderer/src/components/message/MessageList.vue index 25aee7694..2e5a22449 100644 --- a/src/renderer/src/components/message/MessageList.vue +++ b/src/renderer/src/components/message/MessageList.vue @@ -23,7 +23,6 @@
- props.items.map((item) => item.message).filter((message): message is Message => Boolean(message)) -) const minimapMessages = computed(() => { const mapped = props.items.map((item) => { @@ -179,7 +174,6 @@ const minimapMessages = computed(() => { if (current.length > 0) return current return chatStore.variantAwareMessages }) -const retry = useMessageRetry(loadedMessages) // === Constants === const HIGHLIGHT_CLASS = 'selection-highlight' @@ -225,7 +219,7 @@ const getMessageSizeKey = (item: MessageListItem) => { const getVariantSizeKey = (item: MessageListItem) => { const message = item.message if (!message || message.role !== 'assistant') return '' - return chatStore.selectedVariantsMap.get(message.id) ?? '' + return chatStore.selectedVariantsMap[message.id] ?? '' } // === Event Handlers === @@ -247,8 +241,9 @@ const handleCopyImage = async ( await capture.captureMessage({ messageId, parentId, fromTop, modelInfo }) } -const handleRetry = async (index: number) => { - if (await retry.retryFromUserMessage(index)) { +const handleRetry = async (messageId?: string) => { + if (!messageId) return + if (await chatStore.retryFromUserMessage(messageId)) { scrollToBottom(true) } } diff --git a/src/renderer/src/components/message/MessageToolbar.vue b/src/renderer/src/components/message/MessageToolbar.vue index 95a0a14e8..712469fcc 100644 --- a/src/renderer/src/components/message/MessageToolbar.vue +++ b/src/renderer/src/components/message/MessageToolbar.vue @@ -68,14 +68,13 @@ {{ t('thread.toolbar.previousVariant') }} - {{ currentVariantIndex !== undefined ? currentVariantIndex + 1 : 1 }} / - {{ totalVariants }} + {{ (currentVariantIndex ?? 0) + 1 }} / {{ totalVariants }}