Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal
onLoadMore={loadEarlierMessages}
rewindPoints={rewindPoints}
sessionId={sessionId}
startedAt={streamSnapshot?.startedAt}
isAssistantProject={isAssistantProject}
assistantName={assistantName}
/>
Expand Down
3 changes: 3 additions & 0 deletions src/components/chat/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ interface MessageListProps {
/** SDK rewind points — only emitted for visible prompt-level user messages (not tool results or auto-triggers), mapped by position */
rewindPoints?: RewindPoint[];
sessionId?: string;
startedAt?: number;
/** Whether this is an assistant workspace project */
isAssistantProject?: boolean;
/** Assistant name for avatar display */
Expand All @@ -193,6 +194,7 @@ export function MessageList({
onLoadMore,
rewindPoints = [],
sessionId,
startedAt,
isAssistantProject,
assistantName,
}: MessageListProps) {
Expand Down Expand Up @@ -315,6 +317,7 @@ export function MessageList({
content={streamingContent}
isStreaming={isStreaming}
sessionId={sessionId}
startedAt={startedAt!}
toolUses={toolUses}
toolResults={toolResults}
streamingToolOutput={streamingToolOutput}
Expand Down
23 changes: 14 additions & 9 deletions src/components/chat/StreamingMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ interface StreamingMessageProps {
content: string;
isStreaming: boolean;
sessionId?: string;
startedAt: number;
toolUses?: ToolUseInfo[];
toolResults?: ToolResultInfo[];
streamingToolOutput?: string;
Expand Down Expand Up @@ -196,17 +197,20 @@ function ThinkingPhaseLabel() {
return <Shimmer>{text}</Shimmer>;
}

function ElapsedTimer() {
const [elapsed, setElapsed] = useState(0);
const startRef = useRef(0);
function ElapsedTimer({ startedAt }: { startedAt: number }) {
const [elapsed, setElapsed] = useState(() => Math.floor((Date.now() - startedAt) / 1000));

// Reset elapsed when the stream start time changes (e.g. new turn or session switch)
useEffect(() => {
setElapsed(Math.floor((Date.now() - startedAt) / 1000));
}, [startedAt]);

useEffect(() => {
startRef.current = Date.now();
const interval = setInterval(() => {
setElapsed(Math.floor((Date.now() - startRef.current) / 1000));
setElapsed(Math.floor((Date.now() - startedAt) / 1000));
}, 1000);
return () => clearInterval(interval);
}, []);
}, [startedAt]);

const mins = Math.floor(elapsed / 60);
const secs = elapsed % 60;
Expand All @@ -218,7 +222,7 @@ function ElapsedTimer() {
);
}

function StreamingStatusBar({ statusText, onForceStop }: { statusText?: string; onForceStop?: () => void }) {
function StreamingStatusBar({ statusText, onForceStop, startedAt }: { statusText?: string; onForceStop?: () => void; startedAt: number }) {
const displayText = statusText || 'Thinking';

// Parse elapsed seconds from statusText like "Running bash... (45s)"
Expand All @@ -241,7 +245,7 @@ function StreamingStatusBar({ statusText, onForceStop }: { statusText?: string;
)}
</div>
<span className="text-muted-foreground/50">|</span>
<ElapsedTimer />
<ElapsedTimer startedAt={startedAt} />
{isCritical && onForceStop && (
<Button
variant="outline"
Expand All @@ -260,6 +264,7 @@ export function StreamingMessage({
content,
isStreaming,
sessionId,
startedAt,
toolUses = [],
toolResults = [],
streamingToolOutput,
Expand Down Expand Up @@ -542,7 +547,7 @@ export function StreamingMessage({
return t('widget.streaming');
})() : undefined)
|| (content && content.length > 0 ? t('streaming.generating') : undefined)
} onForceStop={onForceStop} />}
} onForceStop={onForceStop} startedAt={startedAt} />}
</MessageContent>
</AIMessage>
);
Expand Down