mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(chat): prevent stale approval UI after SSE error/done
This commit is contained in:
parent
f36c5b0aaa
commit
79c31ead32
@ -659,19 +659,19 @@ const segments = computed<MessageSegment[]>(() => {
|
||||
const useSegmentedView = computed(() => segments.value.length > 1)
|
||||
|
||||
const toolCallsMeta = computed<ToolCallMeta[]>(() => {
|
||||
return props.message.metadata?.toolCalls || []
|
||||
return parsedMetadata.value?.toolCalls || []
|
||||
})
|
||||
|
||||
const browserActionsMeta = computed<BrowserAction[]>(() => {
|
||||
return props.message.metadata?.browserActions || []
|
||||
return parsedMetadata.value?.browserActions || []
|
||||
})
|
||||
|
||||
const planMeta = computed<PlanMeta | undefined>(() => {
|
||||
return props.message.metadata?.plan
|
||||
return parsedMetadata.value?.plan
|
||||
})
|
||||
|
||||
const currentPhaseName = computed(() => {
|
||||
const phase = props.message.metadata?.currentPhase
|
||||
const phase = parsedMetadata.value?.currentPhase
|
||||
switch (phase) {
|
||||
case 'reasoning': return 'Reasoning'
|
||||
case 'action': return 'Executing tools'
|
||||
@ -693,7 +693,9 @@ const truncateArgs = (args: string) => {
|
||||
|
||||
// --- 审批面板 ---
|
||||
const pendingApproval = computed(() => {
|
||||
return props.message.metadata?.pendingApproval || null
|
||||
const approval = parsedMetadata.value?.pendingApproval
|
||||
if (!approval || approval.status === 'expired') return null
|
||||
return approval
|
||||
})
|
||||
|
||||
const approvalSeverityClass = computed(() => {
|
||||
@ -724,7 +726,7 @@ const showExecutionPanel = computed(() => {
|
||||
if (role.value !== 'assistant') return false
|
||||
// 审批卡片有独立的渲染区域,但 execution panel 也应该在审批阶段展示上下文
|
||||
return toolCallsMeta.value.length > 0 || !!planMeta.value
|
||||
|| (isGenerating.value && props.message.metadata?.currentPhase)
|
||||
|| (isGenerating.value && parsedMetadata.value?.currentPhase)
|
||||
|| !!pendingApproval.value
|
||||
})
|
||||
|
||||
|
||||
@ -175,6 +175,44 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
return metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE 以 error/done 结束但审批实际上已失效时,收口残留的 awaiting_approval UI 状态。
|
||||
* 必须用 updateMessage 触发响应式更新,不能只改嵌套字段。
|
||||
*/
|
||||
const expirePendingApprovals = (finalStatus: 'completed' | 'failed' | 'stopped') => {
|
||||
for (const m of messages.value) {
|
||||
if (m.role !== 'assistant') continue
|
||||
const metadata = parseMetadata((m as any).metadata)
|
||||
const pendingApproval = metadata?.pendingApproval
|
||||
const hasPendingApproval = pendingApproval?.status === 'pending_approval'
|
||||
const isAwaitingApprovalMsg = m.status === 'awaiting_approval' || metadata?.currentPhase === 'awaiting_approval'
|
||||
if (!hasPendingApproval && !isAwaitingApprovalMsg) continue
|
||||
if (m.id === undefined || m.id === null) continue
|
||||
|
||||
const toolCalls = Array.isArray(metadata?.toolCalls)
|
||||
? metadata.toolCalls.map((tc: any) => (
|
||||
tc?.status === 'running' || tc?.status === 'awaiting_approval'
|
||||
? { ...tc, status: 'completed' }
|
||||
: tc
|
||||
))
|
||||
: metadata?.toolCalls
|
||||
|
||||
updateMessage(m.id, {
|
||||
...m,
|
||||
status: m.status === 'awaiting_approval' ? finalStatus : m.status,
|
||||
metadata: {
|
||||
...metadata,
|
||||
currentPhase: undefined,
|
||||
runningToolName: undefined,
|
||||
toolCalls,
|
||||
pendingApproval: hasPendingApproval
|
||||
? { ...pendingApproval, status: 'expired' }
|
||||
: pendingApproval,
|
||||
},
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
|
||||
// 消息管理
|
||||
const {
|
||||
messages,
|
||||
@ -384,6 +422,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
: data.status === 'stopped' ? 'stopped' : 'completed'
|
||||
if (data.status !== 'awaiting_approval') {
|
||||
phaseInfo.value = null
|
||||
expirePendingApprovals(data.status === 'stopped' ? 'stopped' : 'completed')
|
||||
}
|
||||
|
||||
// 兜底清理排队状态(如果 queued_input_started 已经处理了则这里是 no-op)
|
||||
@ -431,6 +470,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
|
||||
phaseInfo.value = null
|
||||
// 错误时清理排队状态,避免脏残留
|
||||
messageQueue.clear()
|
||||
expirePendingApprovals('failed')
|
||||
|
||||
if (errorFired) return
|
||||
errorFired = true
|
||||
|
||||
@ -93,6 +93,9 @@ function mergeContentParts(localParts: any[], fetchedParts: any[]): any[] {
|
||||
return merged.sort((a, b) => (order[a.type] ?? 99) - (order[b.type] ?? 99))
|
||||
}
|
||||
|
||||
/** 审批的已决状态(前端通过 SSE 实时更新,比后端持久化更新) */
|
||||
const RESOLVED_APPROVAL = new Set(['expired', 'approved', 'denied'])
|
||||
|
||||
function mergeMetadata(localMetaRaw: any, fetchedMetaRaw: any): Record<string, any> {
|
||||
const localMeta = safeParseMeta(localMetaRaw)
|
||||
const fetchedMeta = safeParseMeta(fetchedMetaRaw)
|
||||
@ -114,7 +117,16 @@ function mergeMetadata(localMetaRaw: any, fetchedMetaRaw: any): Record<string, a
|
||||
merged.toolCalls = fetchedToolCalls
|
||||
}
|
||||
|
||||
if (!merged.pendingApproval && localMeta.pendingApproval) {
|
||||
// pendingApproval:前端已决/过期状态不被后端的 pending 状态回退
|
||||
const localApprovalStatus = localMeta.pendingApproval?.status
|
||||
if (RESOLVED_APPROVAL.has(localApprovalStatus)) {
|
||||
merged.pendingApproval = localMeta.pendingApproval
|
||||
// 清除已过期审批关联的 phase 字段,防止 UI 残留
|
||||
if (localApprovalStatus === 'expired') {
|
||||
delete merged.currentPhase
|
||||
delete merged.runningToolName
|
||||
}
|
||||
} else if (!merged.pendingApproval && localMeta.pendingApproval) {
|
||||
merged.pendingApproval = localMeta.pendingApproval
|
||||
}
|
||||
|
||||
@ -142,7 +154,10 @@ function mergeAssistantMessages(localMsg: Message, fetchedMsg: Message): Message
|
||||
: localMsg.content,
|
||||
contentParts,
|
||||
metadata: mergeMetadata(localMsg.metadata, fetchedMsg.metadata),
|
||||
status: fetchedMsg.status || localMsg.status,
|
||||
// 不要把已终结的状态(failed/completed/stopped)回退为 awaiting_approval
|
||||
status: (fetchedMsg.status === 'awaiting_approval' && localMsg.status && localMsg.status !== 'awaiting_approval')
|
||||
? localMsg.status
|
||||
: (fetchedMsg.status || localMsg.status),
|
||||
promptTokens: fetchedMsg.promptTokens ?? localMsg.promptTokens,
|
||||
completionTokens: fetchedMsg.completionTokens ?? localMsg.completionTokens,
|
||||
createTime: fetchedMsg.createTime || localMsg.createTime,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user