Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import { useLayoutEffect, useRef, useState } from "react";
|
|
import type { AgentConversationVM } from "../../../shared/agentConversation/contracts/agentConversationVM";
|
|
import type { AgentGUINodeViewModel } from "../model/agentGuiNodeTypes";
|
|
|
|
export const AGENT_GUI_TIMELINE_SKELETON_DELAY_MS = 300;
|
|
|
|
interface Input {
|
|
activeConversationId: string | null;
|
|
availability: AgentGUINodeViewModel["detail"]["availability"];
|
|
conversation: AgentConversationVM | null;
|
|
}
|
|
|
|
interface StableTimeline {
|
|
conversation: AgentConversationVM | null;
|
|
conversationId: string | null;
|
|
}
|
|
|
|
export function useAgentGUITimelineTransition(input: Input) {
|
|
const activeConversationId = input.activeConversationId;
|
|
const committedTimelineRef = useRef<StableTimeline>({
|
|
conversation: input.conversation,
|
|
conversationId: activeConversationId
|
|
});
|
|
const [revealedSkeletonConversationId, setRevealedSkeletonConversationId] =
|
|
useState<string | null>(null);
|
|
const revealedSkeletonConversationIdRef = useRef<string | null>(null);
|
|
const transitionPending =
|
|
activeConversationId !== null &&
|
|
input.availability === "loading" &&
|
|
(!input.conversation || input.conversation.rows.length === 0);
|
|
|
|
useLayoutEffect(() => {
|
|
if (!transitionPending || activeConversationId === null) {
|
|
committedTimelineRef.current = {
|
|
conversation: input.conversation,
|
|
conversationId: activeConversationId
|
|
};
|
|
if (revealedSkeletonConversationIdRef.current !== null) {
|
|
revealedSkeletonConversationIdRef.current = null;
|
|
setRevealedSkeletonConversationId(null);
|
|
}
|
|
return;
|
|
}
|
|
// timing: avoid flashing a detail skeleton during fast local conversation loads
|
|
const timeoutId = window.setTimeout(() => {
|
|
revealedSkeletonConversationIdRef.current = activeConversationId;
|
|
setRevealedSkeletonConversationId(activeConversationId);
|
|
}, AGENT_GUI_TIMELINE_SKELETON_DELAY_MS);
|
|
return () => window.clearTimeout(timeoutId);
|
|
}, [activeConversationId, input.conversation, transitionPending]);
|
|
|
|
const showTimelineSkeleton =
|
|
transitionPending &&
|
|
revealedSkeletonConversationId === activeConversationId;
|
|
const retainedTimeline = transitionPending
|
|
? committedTimelineRef.current
|
|
: null;
|
|
const retainsPreviousTimeline =
|
|
!showTimelineSkeleton &&
|
|
retainedTimeline?.conversation !== null &&
|
|
retainedTimeline?.conversation !== undefined;
|
|
|
|
return {
|
|
conversation: transitionPending
|
|
? (retainedTimeline?.conversation ?? null)
|
|
: input.conversation,
|
|
showTimelineSkeleton,
|
|
timelineConversationId: retainsPreviousTimeline
|
|
? (retainedTimeline?.conversationId ?? activeConversationId)
|
|
: activeConversationId,
|
|
transitionPending
|
|
};
|
|
}
|