diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming.tsx index 7dfe9af4ea..f3d8db9dd8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/smooth-streaming.tsx @@ -93,10 +93,14 @@ function calculateAdaptiveDelay(displayedLength: number, totalLength: number): n */ export const SmoothStreamingText = memo( ({ content, isStreaming }: SmoothStreamingTextProps) => { - const [displayedContent, setDisplayedContent] = useState('') + // Initialize with full content when not streaming to avoid flash on page load + const [displayedContent, setDisplayedContent] = useState(() => + isStreaming ? '' : content + ) const contentRef = useRef(content) const rafRef = useRef(null) - const indexRef = useRef(0) + // Initialize index based on streaming state + const indexRef = useRef(isStreaming ? 0 : content.length) const lastFrameTimeRef = useRef(0) const isAnimatingRef = useRef(false) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx index fbb7065f90..234b90b009 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block.tsx @@ -46,12 +46,16 @@ interface SmoothThinkingTextProps { */ const SmoothThinkingText = memo( ({ content, isStreaming }: SmoothThinkingTextProps) => { - const [displayedContent, setDisplayedContent] = useState('') + // Initialize with full content when not streaming to avoid flash on page load + const [displayedContent, setDisplayedContent] = useState(() => + isStreaming ? '' : content + ) const [showGradient, setShowGradient] = useState(false) const contentRef = useRef(content) const textRef = useRef(null) const rafRef = useRef(null) - const indexRef = useRef(0) + // Initialize index based on streaming state + const indexRef = useRef(isStreaming ? 0 : content.length) const lastFrameTimeRef = useRef(0) const isAnimatingRef = useRef(false) diff --git a/apps/sim/stores/panel/copilot/store.ts b/apps/sim/stores/panel/copilot/store.ts index 64d1d3e7bf..a5c8c8873f 100644 --- a/apps/sim/stores/panel/copilot/store.ts +++ b/apps/sim/stores/panel/copilot/store.ts @@ -422,7 +422,8 @@ function abortAllInProgressTools(set: any, get: () => CopilotStore) { * Loads messages from DB for UI rendering. * Messages are stored exactly as they render, so we just need to: * 1. Register client tool instances for any tool calls - * 2. Return the messages as-is + * 2. Clear any streaming flags (messages loaded from DB are never actively streaming) + * 3. Return the messages */ function normalizeMessagesForUI(messages: CopilotMessage[]): CopilotMessage[] { try { @@ -438,23 +439,57 @@ function normalizeMessagesForUI(messages: CopilotMessage[]): CopilotMessage[] { } } - // Register client tool instances for all tool calls so they can be looked up + // Register client tool instances and clear streaming flags for all tool calls for (const message of messages) { + // Clear from contentBlocks (current format) if (message.contentBlocks) { for (const block of message.contentBlocks as any[]) { if (block?.type === 'tool_call' && block.toolCall) { registerToolCallInstances(block.toolCall) + clearStreamingFlags(block.toolCall) } } } + // Clear from toolCalls array (legacy format) + if (message.toolCalls) { + for (const toolCall of message.toolCalls) { + registerToolCallInstances(toolCall) + clearStreamingFlags(toolCall) + } + } } - // Return messages as-is - they're already in the correct format for rendering + // Return messages - they're already in the correct format for rendering return messages } catch { return messages } } +/** + * Recursively clears streaming flags from a tool call and its nested subagent tool calls. + * This ensures messages loaded from DB don't appear to be streaming. + */ +function clearStreamingFlags(toolCall: any): void { + if (!toolCall) return + + // Always set subAgentStreaming to false - messages loaded from DB are never streaming + toolCall.subAgentStreaming = false + + // Clear nested subagent tool calls + if (Array.isArray(toolCall.subAgentBlocks)) { + for (const block of toolCall.subAgentBlocks) { + if (block?.type === 'subagent_tool_call' && block.toolCall) { + clearStreamingFlags(block.toolCall) + } + } + } + if (Array.isArray(toolCall.subAgentToolCalls)) { + for (const subTc of toolCall.subAgentToolCalls) { + clearStreamingFlags(subTc) + } + } +} + /** * Recursively registers client tool instances for a tool call and its nested subagent tool calls. */