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

114 lines
3.5 KiB
TypeScript

import type { RefObject } from "react";
import type { AgentTranscriptVirtualScrollController } from "../../../shared/agentConversation/components/AgentTranscriptView";
import styles from "../AgentGUINode.styles";
export interface TimelineGeometry {
clientHeight: number;
maxScrollTop: number;
scrollHeight: number;
}
export interface BottomDockSafeArea {
bottomDock: HTMLDivElement;
floatingOverflowHeight: number;
revision: string;
timelineOverflowHeight: number;
}
export function readTimelineGeometry(timeline: HTMLElement): TimelineGeometry {
const scrollHeight = timeline.scrollHeight;
const clientHeight = timeline.clientHeight;
return {
clientHeight,
maxScrollTop: Math.max(0, scrollHeight - clientHeight),
scrollHeight
};
}
export function matchingVirtualScrollController(
controllerRef: RefObject<AgentTranscriptVirtualScrollController | null>,
agentSessionId: string
): AgentTranscriptVirtualScrollController | null {
const controller = controllerRef.current;
return controller?.enabled && controller.agentSessionId === agentSessionId
? controller
: null;
}
export function hasStaleVirtualScrollController(
controllerRef: RefObject<AgentTranscriptVirtualScrollController | null>,
agentSessionId: string
): boolean {
const controller = controllerRef.current;
return (
controller?.enabled === true && controller.agentSessionId !== agentSessionId
);
}
export function userScrollBehavior(): ScrollBehavior {
return typeof window.matchMedia === "function" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
? "auto"
: "smooth";
}
export function readBottomDockSafeArea(bottomDock: HTMLDivElement): {
floatingOverflowHeight: number;
timelineOverflowHeight: number;
} {
const bottomDockRect = bottomDock.getBoundingClientRect();
let timelineVisualTop = bottomDockRect.top;
let floatingVisualTop = bottomDockRect.top;
bottomDock.querySelectorAll("*").forEach((element) => {
if (element.closest(`.${styles.bottomDockScrollToBottom}`)) {
return;
}
const rect = element.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) {
return;
}
// The prompt input expands above the dock without changing the timeline's
// reserved safe area. Its outer input box still moves floating controls.
if (element.closest(`.${styles.composerInputShell}`)) {
if (element.matches(".agent-gui-node__composer-prompt-input-area")) {
floatingVisualTop = Math.min(floatingVisualTop, rect.top);
}
return;
}
// Disclosure overlays affect floating controls, not transcript spacing.
if (element.closest(`.${styles.composerDisclosurePanel}`)) {
floatingVisualTop = Math.min(floatingVisualTop, rect.top);
return;
}
floatingVisualTop = Math.min(floatingVisualTop, rect.top);
timelineVisualTop = Math.min(timelineVisualTop, rect.top);
});
return {
timelineOverflowHeight: Math.max(
0,
Math.ceil(bottomDockRect.top - timelineVisualTop)
),
floatingOverflowHeight: Math.max(
0,
Math.ceil(bottomDockRect.top - floatingVisualTop)
)
};
}
export function writeBottomDockSafeArea(
timeline: HTMLDivElement,
safeArea: Pick<
BottomDockSafeArea,
"bottomDock" | "floatingOverflowHeight" | "timelineOverflowHeight"
>
): void {
timeline.style.setProperty(
"--agent-gui-bottom-dock-safe-area",
`${safeArea.timelineOverflowHeight}px`
);
safeArea.bottomDock.style.setProperty(
"--agent-gui-bottom-dock-floating-safe-area",
`${safeArea.floatingOverflowHeight}px`
);
}