fix: resolve chat message duplication and empty thinking blocks

- Refactor useApp.ts to pass through original data without accumulation
  - ChatView is now the single source of truth for chunk accumulation
  - Add empty content check in ThoughtBlockView to skip rendering whitespace-only thoughts

  Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-01-15 09:50:54 +08:00
parent ee7e3506e3
commit ec7fe82726
2 changed files with 8 additions and 56 deletions

View File

@@ -377,6 +377,9 @@ function TextContentBlock({ content }: { content: string }) {
// Thought block view - expanded, collapsible
function ThoughtBlockView({ text }: { text: string }) {
// Skip rendering if content is empty or only whitespace
if (!text || !text.trim()) return null
const [isExpanded, setIsExpanded] = useState(false)
const isLong = text.length > 200

View File

@@ -1,7 +1,7 @@
/**
* Main application state hook
*/
import { useState, useEffect, useCallback, useRef } from 'react'
import { useState, useEffect, useCallback } from 'react'
import type {
MulticaSession,
StoredSessionUpdate,
@@ -56,9 +56,6 @@ export function useApp(): AppState & AppActions {
? runningSessionsStatus.processingSessionIds.includes(currentSession.id)
: false
// Track streaming text for current message
const streamingTextRef = useRef<string>('')
// Load sessions on mount
useEffect(() => {
loadSessions()
@@ -71,71 +68,24 @@ export function useApp(): AppState & AppActions {
// Subscribe to agent events
useEffect(() => {
const unsubMessage = window.electronAPI.onAgentMessage((message) => {
// Log all incoming ACP messages for debugging
console.log('[ACP Message]', JSON.stringify(message, null, 2))
// Only process messages for the current session
// message.sessionId is ACP Agent Session ID, compare with currentAgentSessionId
if (!currentAgentSessionId || message.sessionId !== currentAgentSessionId) {
console.log('[ACP] Ignoring message for different session:', message.sessionId, 'current:', currentAgentSessionId)
return
}
const update = message.update
const updateType = update?.sessionUpdate
console.log('[ACP Update Type]', updateType)
// Handle streaming text accumulation for agent messages
if (updateType === 'agent_message_chunk' && update.content?.type === 'text') {
streamingTextRef.current += update.content.text
}
// Add the update to session updates for real-time display
// Pass through original update without any accumulation
// ChatView is responsible for accumulating chunks into complete messages
setSessionUpdates((prev) => {
const now = new Date().toISOString()
// For agent_message_chunk, we want to show accumulated text
if (updateType === 'agent_message_chunk') {
const streamingUpdate: StoredSessionUpdate = {
timestamp: now,
update: {
sessionId: message.sessionId,
update: {
sessionUpdate: 'agent_message_chunk',
content: { type: 'text', text: streamingTextRef.current },
},
},
}
// Replace last streaming update if it was also agent_message_chunk
const lastIdx = prev.length - 1
if (lastIdx >= 0) {
const last = prev[lastIdx]
if (
last.update.update &&
'sessionUpdate' in last.update.update &&
last.update.update.sessionUpdate === 'agent_message_chunk'
) {
return [...prev.slice(0, lastIdx), streamingUpdate]
}
}
return [...prev, streamingUpdate]
}
// For all other update types, add them directly
const newUpdate = {
timestamp: now,
timestamp: new Date().toISOString(),
update: {
sessionId: message.sessionId,
update: update,
update: message.update,
},
} as StoredSessionUpdate
return [...prev, newUpdate]
})
if (message.done) {
streamingTextRef.current = ''
}
})
const unsubStatus = window.electronAPI.onAgentStatus((status) => {
@@ -238,7 +188,6 @@ export function useApp(): AppState & AppActions {
try {
setError(null)
streamingTextRef.current = ''
// Add user message to updates (use a custom marker for UI display)
// 'user_message' is a custom type not in ACP SDK, used for UI purposes only