mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(goal,ui): guard evaluating flag against the message_complete race
This commit is contained in:
parent
7d7ea99747
commit
34175ab94d
@ -546,12 +546,19 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
// Goal-evaluator breathing halo: when an assistant message finishes
|
||||
// and this conversation has an active goal, the backend's evaluation
|
||||
// node runs next. Flip the per-conv flag so GoalAvatarRing paints the
|
||||
// breathing halo until `goal_evaluated` resets it. Skip when no goal
|
||||
// is active — the halo should be quiet for ordinary turns.
|
||||
// breathing halo until `goal_evaluated` resets it.
|
||||
//
|
||||
// Skip when:
|
||||
// - the conversation has no active goal (ordinary turn, no halo)
|
||||
// - the evaluator already fired in this turn (SSE order under the
|
||||
// structured stream is goal_evaluated → done → message_complete,
|
||||
// so re-arming here would leave the halo stuck on after the
|
||||
// evaluator already cleared it)
|
||||
if (
|
||||
data.status === 'completed'
|
||||
&& streamConversationId
|
||||
&& goalStore.activeGoal(streamConversationId)
|
||||
&& !goalStore.recentlyEvaluated(streamConversationId)
|
||||
) {
|
||||
goalStore.markEvaluating(streamConversationId, true)
|
||||
}
|
||||
|
||||
@ -54,6 +54,19 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
// the message's `metadata.fromFollowup` flag (handled by ChatHistory).
|
||||
const followupMessageIdsByConv = ref<Record<string, Set<string>>>({})
|
||||
|
||||
// Timestamp (epoch ms) of the most recent terminal evaluator event for
|
||||
// each conversation — used by useChat's message_complete handler to
|
||||
// avoid re-setting the breathing-halo flag on the trailing edge of a
|
||||
// turn whose evaluator already finished. Backend SSE order under the
|
||||
// structured stream is goal_evaluated → done → message_complete, so
|
||||
// without this guard the halo would re-light right after it cleared.
|
||||
const lastTerminalEventAtByConv = ref<Record<string, number>>({})
|
||||
/** Window during which message_complete suppresses re-setting evaluating
|
||||
* after a goal_evaluated / goal_completed / goal_exhausted just landed.
|
||||
* 4s leaves headroom for SSE jitter without blocking a genuine next
|
||||
* turn from re-arming the halo. */
|
||||
const TERMINAL_EVENT_SUPPRESSION_MS = 4000
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadActiveForConversation(conversationId: string) {
|
||||
@ -156,6 +169,7 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
switch (eventType) {
|
||||
case 'goal_evaluated': {
|
||||
evaluatingByConv.value[conversationId] = false
|
||||
lastTerminalEventAtByConv.value[conversationId] = Date.now()
|
||||
if (goal && data?.score != null) {
|
||||
goal.completionScore = Number(data.score)
|
||||
}
|
||||
@ -175,6 +189,7 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
}
|
||||
case 'goal_completed': {
|
||||
evaluatingByConv.value[conversationId] = false
|
||||
lastTerminalEventAtByConv.value[conversationId] = Date.now()
|
||||
// Capture the terminal snapshot BEFORE we null out the cache so
|
||||
// the GoalSystemLine has a title + score to render.
|
||||
recentTerminalByConv.value[conversationId] = {
|
||||
@ -192,6 +207,7 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
}
|
||||
case 'goal_exhausted': {
|
||||
evaluatingByConv.value[conversationId] = false
|
||||
lastTerminalEventAtByConv.value[conversationId] = Date.now()
|
||||
recentTerminalByConv.value[conversationId] = {
|
||||
status: 'exhausted',
|
||||
title: goal?.title || '目标',
|
||||
@ -228,6 +244,21 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
evaluatingByConv.value[conversationId] = flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a terminal evaluator event (goal_evaluated,
|
||||
* goal_completed, goal_exhausted) arrived for this conversation
|
||||
* within the suppression window — i.e. the evaluator has already
|
||||
* finished this turn and the halo shouldn't be re-armed.
|
||||
* Callers (useChat's message_complete handler) consult this before
|
||||
* setting evaluating=true so the SSE order goal_evaluated → done →
|
||||
* message_complete doesn't leave a permanently-lit ring.
|
||||
*/
|
||||
function recentlyEvaluated(conversationId: string): boolean {
|
||||
const at = lastTerminalEventAtByConv.value[conversationId]
|
||||
if (!at) return false
|
||||
return Date.now() - at < TERMINAL_EVENT_SUPPRESSION_MS
|
||||
}
|
||||
|
||||
function isEvaluating(conversationId: string): boolean {
|
||||
return Boolean(evaluatingByConv.value[conversationId])
|
||||
}
|
||||
@ -314,6 +345,7 @@ export const useGoalStore = defineStore('goal', () => {
|
||||
loadEvents,
|
||||
handleSseEvent,
|
||||
markEvaluating,
|
||||
recentlyEvaluated,
|
||||
isEvaluating,
|
||||
activeGoal,
|
||||
progressFraction,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user