import type { Bot, ComputerStatus, Group, ProductEvent, ThreadSnapshot } from "@rakazo/contracts"; import type { MutableRefObject } from "react"; import { isComputerStatusEvent, isThreadSnapshotEvent, reduceComputerStatus, reduceThreadSnapshot, } from "../../lib/thread-events"; const THREAD_SNAPSHOT_TIMEOUT_MS = 2_000; export function threadSnapshotSignal(parent: AbortSignal): AbortSignal { return AbortSignal.any([parent, AbortSignal.timeout(THREAD_SNAPSHOT_TIMEOUT_MS)]); } export function firstThreadRoute( bots: readonly Pick[], groups: readonly Pick[], /** In a project the user lands in its main conversation. */ inProject = false, ): string { const main = inProject ? groups.find((group) => group.kind === "main") : undefined; if (main) return `/app/g/${main.id}`; if (bots[0]) return `/app/${bots[0].id}`; if (groups[0]) return `/app/g/${groups[0].id}`; return "/app"; } export function applyThreadEvent( event: ProductEvent, commitSnapshot: (next: ThreadSnapshot | null) => void, commitComputer: (next: ComputerStatus | null) => void, snapshotRef: MutableRefObject, computerRef: MutableRefObject, ) { if (isThreadSnapshotEvent(event)) { const next = reduceThreadSnapshot(snapshotRef.current, event); commitSnapshot(next); } if (isComputerStatusEvent(event)) { const next = reduceComputerStatus(computerRef.current, event); commitComputer(next); } } export function newClientNonce(): string { const webCrypto = globalThis.crypto; if (webCrypto && typeof webCrypto.randomUUID === "function") { return webCrypto.randomUUID(); } return `m-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`; }