Use daemon timestamps for turn timing (#1033)

This commit is contained in:
Mohamed Boudra
2026-05-15 11:09:13 +08:00
committed by GitHub
parent 1962225647
commit a72c7f6c4b
22 changed files with 1284 additions and 193 deletions
@@ -35,13 +35,14 @@ import { Check, ChevronDown, X } from "lucide-react-native";
import { usePanelStore } from "@/stores/panel-store";
import {
AssistantMessage,
AssistantTurnFooter,
SpeakMessage,
UserMessage,
ActivityLog,
ToolCall,
TodoListCard,
CompactionMarker,
TurnCopyButton,
LiveElapsed,
MessageOuterSpacingProvider,
type InlinePathTarget,
} from "./message";
@@ -89,6 +90,7 @@ import {
WORKING_INDICATOR_CYCLE_MS,
WORKING_INDICATOR_OFFSETS,
} from "@/utils/working-indicator";
import { findInFlightTurnStartedAt, findTurnHeaderForAssistantTurn } from "@/timeline/turn-time";
import { isWeb } from "@/constants/platform";
import { SPACING, type Theme } from "@/styles/theme";
@@ -274,6 +276,15 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
),
[agent.status, baseRenderModel.segments.liveHead, streamRenderStrategy],
);
const inFlightTurnStartedAt = useMemo(
() =>
findInFlightTurnStartedAt({
agentStatus: agent.status,
liveHead: baseRenderModel.segments.liveHead,
tail: streamItems,
}),
[agent.status, baseRenderModel.segments.liveHead, streamItems],
);
useImperativeHandle(
ref,
() => ({
@@ -549,6 +560,12 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
/>
);
case "turn_header":
// Purely a data carrier - duration renders next to the assistant
// turn's copy button (TurnCopyButtonSlot), and the in-flight
// ticker renders next to the WorkingIndicator dots.
return null;
default:
return null;
}
@@ -583,7 +600,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
item.kind === "assistant_message" && item.id === inlineWorkingIndicatorItemId;
let footer: ReactNode = null;
if (isRunningAssistantTurnFooter) {
footer = <InlineWorkingIndicatorSlot />;
footer = <InlineWorkingIndicatorSlot inFlightTurnStartedAt={inFlightTurnStartedAt} />;
} else if (isEndOfAssistantTurn) {
footer = (
<TurnCopyButtonSlot strategy={streamRenderStrategy} items={items} startIndex={index} />
@@ -603,6 +620,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
agent.status,
streamRenderStrategy,
inlineWorkingIndicatorItemId,
inFlightTurnStartedAt,
],
);
@@ -628,10 +646,10 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
() =>
showAuxiliaryWorkingIndicator ? (
<View style={stylesheet.bottomBarWrapper} testID="stream-working-indicator-auxiliary">
<WorkingIndicator />
<WorkingIndicator inFlightTurnStartedAt={inFlightTurnStartedAt} />
</View>
) : null,
[showAuxiliaryWorkingIndicator],
[showAuxiliaryWorkingIndicator, inFlightTurnStartedAt],
);
const renderModel = useMemo<AgentStreamRenderModel>(() => {
return {
@@ -803,7 +821,13 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
export const AgentStreamView = memo(AgentStreamViewComponent);
AgentStreamView.displayName = "AgentStreamView";
function WorkingIndicator({ variant = "auxiliary" }: { variant?: "auxiliary" | "inline" }) {
function WorkingIndicator({
variant = "auxiliary",
inFlightTurnStartedAt = null,
}: {
variant?: "auxiliary" | "inline";
inFlightTurnStartedAt?: Date | null;
}) {
const progress = useSharedValue(0);
useEffect(() => {
@@ -867,14 +891,25 @@ function WorkingIndicator({ variant = "auxiliary" }: { variant?: "auxiliary" | "
<Animated.View style={dotTwoCombinedStyle} />
<Animated.View style={dotThreeCombinedStyle} />
</View>
{inFlightTurnStartedAt ? (
<LiveElapsed
startedAt={inFlightTurnStartedAt}
style={stylesheet.workingElapsed}
testID="turn-working-elapsed"
/>
) : null}
</View>
);
}
function InlineWorkingIndicatorSlot() {
function InlineWorkingIndicatorSlot({
inFlightTurnStartedAt,
}: {
inFlightTurnStartedAt: Date | null;
}) {
return (
<View style={stylesheet.inlineTurnFooter} testID="turn-working-indicator">
<WorkingIndicator variant="inline" />
<WorkingIndicator variant="inline" inFlightTurnStartedAt={inFlightTurnStartedAt} />
</View>
);
}
@@ -900,7 +935,17 @@ function TurnCopyButtonSlot({ strategy, items, startIndex }: TurnCopyButtonSlotP
}),
[strategy, items, startIndex],
);
return <TurnCopyButton getContent={getContent} />;
const header = useMemo(
() => findTurnHeaderForAssistantTurn({ strategy, items, startIndex }),
[strategy, items, startIndex],
);
return (
<AssistantTurnFooter
getContent={getContent}
startedAt={header?.startedAt}
durationMs={header?.durationMs}
/>
);
}
interface ToolCallSlotProps extends Omit<
@@ -1251,8 +1296,15 @@ const stylesheet = StyleSheet.create((theme) => ({
},
inlineWorkingIndicatorFrame: {
height: 18,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
justifyContent: "flex-start",
gap: theme.spacing[2],
},
workingElapsed: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontVariant: ["tabular-nums"],
},
workingIndicatorBubble: {
flexDirection: "row",