Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

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
};
}