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

108 lines
3.1 KiB
TypeScript

import { createContext, useContext, type JSX, type ReactNode } from "react";
import {
useExternalStoreSnapshot,
type ExternalStoreSnapshotSource
} from "@tutti-os/ui-react-hooks";
const AgentConversationClockVisibilityContext = createContext(true);
type ClockListener = () => void;
class AgentConversationClockStore {
private readonly minuteListeners = new Set<ClockListener>();
private readonly secondListeners = new Set<ClockListener>();
private minuteTimeMs = Date.now();
private minuteTimer: number | null = null;
private secondTimeMs = Date.now();
private secondTimer: number | null = null;
readonly disabled: ExternalStoreSnapshotSource<number> = {
getSnapshot: () => 0,
subscribe: () => () => undefined
};
readonly minute: ExternalStoreSnapshotSource<number> = {
getSnapshot: () => this.minuteTimeMs,
subscribe: (listener) =>
this.subscribe(
listener,
this.minuteListeners,
60_000,
"minuteTimeMs",
"minuteTimer"
)
};
readonly second: ExternalStoreSnapshotSource<number> = {
getSnapshot: () => this.secondTimeMs,
subscribe: (listener) =>
this.subscribe(
listener,
this.secondListeners,
1_000,
"secondTimeMs",
"secondTimer"
)
};
private subscribe(
listener: ClockListener,
listeners: Set<ClockListener>,
intervalMs: number,
timeKey: "minuteTimeMs" | "secondTimeMs",
timerKey: "minuteTimer" | "secondTimer"
): () => void {
listeners.add(listener);
if (listeners.size === 1) {
this[timeKey] = Date.now();
// timing: share one cadence timer across all mounted consumers.
this[timerKey] = window.setInterval(() => {
this[timeKey] = Date.now();
listeners.forEach((candidate) => candidate());
}, intervalMs);
}
return () => {
listeners.delete(listener);
if (listeners.size === 0) {
const timer = this[timerKey];
if (timer !== null) {
window.clearInterval(timer);
this[timerKey] = null;
}
}
};
}
}
const agentConversationClock = new AgentConversationClockStore();
export function AgentConversationClockProvider({
children,
isVisible
}: {
children: ReactNode;
isVisible: boolean;
}): JSX.Element {
return (
<AgentConversationClockVisibilityContext.Provider value={isVisible}>
{children}
</AgentConversationClockVisibilityContext.Provider>
);
}
export function useAgentConversationNowUnixMs(enabled: boolean): number | null {
const isVisible = useContext(AgentConversationClockVisibilityContext);
const shouldTick = enabled && isVisible;
const nowUnixMs = useExternalStoreSnapshot(
shouldTick ? agentConversationClock.second : agentConversationClock.disabled
);
return shouldTick ? nowUnixMs : null;
}
export function useAgentConversationMinuteNowUnixMs(): number {
const isVisible = useContext(AgentConversationClockVisibilityContext);
const nowUnixMs = useExternalStoreSnapshot(
isVisible ? agentConversationClock.minute : agentConversationClock.disabled
);
return isVisible ? nowUnixMs : agentConversationClock.minute.getSnapshot();
}