import React from 'react'; import type { Part } from '@opencode-ai/sdk/v2'; import { MarkdownRenderer } from '../../MarkdownRenderer'; import type { StreamPhase } from '../types'; import type { ContentChangeReason } from '@/hooks/useChatScrollManager'; import { ReasoningTimelineBlock, formatReasoningText } from './ReasoningPart'; type PartWithText = Part & { text?: string; content?: string; value?: string; time?: { start?: number; end?: number } }; interface AssistantTextPartProps { part: Part; messageId: string; streamPhase: StreamPhase; allowAnimation: boolean; onContentChange?: (reason?: ContentChangeReason, messageId?: string) => void; renderAsReasoning?: boolean; } const AssistantTextPart: React.FC = ({ part, messageId, streamPhase, allowAnimation, onContentChange, renderAsReasoning = false, }) => { const partWithText = part as PartWithText; const rawText = partWithText.text; const baseTextContent = typeof rawText === 'string' ? rawText : partWithText.content || partWithText.value || ''; const textContent = React.useMemo(() => { if (renderAsReasoning) { return formatReasoningText(baseTextContent); } return baseTextContent; }, [baseTextContent, renderAsReasoning]); const isStreamingPhase = streamPhase === 'streaming'; const isCooldownPhase = streamPhase === 'cooldown'; const wasStreamingRef = React.useRef(isStreamingPhase); if (isStreamingPhase || isCooldownPhase) { wasStreamingRef.current = true; return null; } const time = partWithText.time; const isFinalized = time && typeof time.end !== 'undefined'; if (!isFinalized && (!textContent || textContent.trim().length === 0)) { return null; } if (!textContent || textContent.trim().length === 0) { return null; } if (renderAsReasoning) { return ( ); } return (
); }; export default AssistantTextPart;