@tangle-network/agent-app 0.46.2 → 0.46.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assistant/index.js +2 -1
- package/dist/assistant/index.js.map +1 -1
- package/dist/chunk-KKBTPZIE.js +805 -0
- package/dist/chunk-KKBTPZIE.js.map +1 -0
- package/dist/{chunk-2RTYQU4W.js → chunk-LEGZX5MQ.js} +150 -4
- package/dist/chunk-LEGZX5MQ.js.map +1 -0
- package/dist/{chunk-MBKNKAN2.js → chunk-RZ6MYWMJ.js} +416 -1210
- package/dist/chunk-RZ6MYWMJ.js.map +1 -0
- package/dist/stories/studio/StudioProviders.d.ts +10 -0
- package/dist/stories/studio/fixtures.d.ts +10 -2
- package/dist/studio/audio-preview.d.ts +12 -0
- package/dist/studio/generation.d.ts +22 -1
- package/dist/studio/index.d.ts +2 -0
- package/dist/studio/index.js +29 -1
- package/dist/studio/ports.d.ts +54 -0
- package/dist/studio-react/composer-option-controls.d.ts +19 -8
- package/dist/studio-react/download-generations.d.ts +4 -0
- package/dist/studio-react/index.d.ts +38 -22
- package/dist/studio-react/index.js +2198 -545
- package/dist/studio-react/index.js.map +1 -1
- package/dist/studio-react/media-tile.d.ts +21 -0
- package/dist/studio-react/media-viewer.d.ts +12 -0
- package/dist/studio-react/studio-confirm.d.ts +8 -0
- package/dist/studio-react/studio-generation-screen.d.ts +42 -0
- package/dist/studio-react/studio-history-screen.d.ts +28 -0
- package/dist/studio-react/studio-home-screen.d.ts +20 -0
- package/dist/studio-react/studio-playback.d.ts +31 -0
- package/dist/studio-react/studio-toasts.d.ts +21 -0
- package/dist/studio-react/studio.css +142 -72
- package/dist/studio-react/use-batch-navigation.d.ts +20 -0
- package/dist/studio-react/use-deferred-delete.d.ts +19 -0
- package/dist/studio-react/use-generation-history.d.ts +22 -0
- package/dist/studio-react/vault-path-popover.d.ts +12 -0
- package/dist/web-react/index.js +8 -6
- package/package.json +2 -7
- package/dist/chunk-2RTYQU4W.js.map +0 -1
- package/dist/chunk-MBKNKAN2.js.map +0 -1
- package/dist/studio-react/generation-card.d.ts +0 -9
- package/dist/studio-react/generation-detail-modal.d.ts +0 -11
- package/dist/studio-react/generation-detail.d.ts +0 -6
- package/dist/studio-react/generation-grid.d.ts +0 -15
- package/dist/studio-react/library-drawer.d.ts +0 -12
- package/dist/studio-react/library-panel.d.ts +0 -15
- package/dist/studio-react/result-canvas.d.ts +0 -6
- package/dist/studio-react/studio-header.d.ts +0 -5
- package/dist/studio-react/studio-sheet.d.ts +0 -12
- package/dist/studio-react/studio-workspace.d.ts +0 -21
- package/dist/studio-react/type-config.d.ts +0 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web-react/session-history.tsx"],"sourcesContent":["/**\n * The rendered half of the session shell: the history view behind the rail's\n * session list, and the rename/delete dialogs both surfaces drive.\n *\n * Storage is a seam, not a dependency. Every product keeps sessions somewhere\n * different (gtm threads, tax sessions, legal matters), so this takes a\n * `fetchPage` data port and injected mutations — the same shape\n * `AgentActivityPanel` (`fetchActivity`) and `ReviewQueuePanel` (`fetchQueue`)\n * already use, rather than a fifth pattern.\n *\n * sandbox-ui free on purpose: `/web-react` must not force the optional peer, so\n * these render on the shared design tokens like the rest of the subpath. The\n * pure logic (nav items, routing, cookies, merging) lives in `/session-shell`,\n * which a server loader can import without pulling React.\n */\n\nimport {\n type ReactNode,\n type RefObject,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport {\n type SessionPage,\n type SessionRailAction,\n type SessionSort,\n type SessionSummary,\n mergeSessionPages,\n sessionLabel,\n UNTITLED_SESSION_LABEL,\n} from '../session-shell/index'\nimport { OVERLAY_SHADOW, PopoverSurface, usePopover } from './controls'\n\n// ---------------------------------------------------------------------------\n// useInfiniteScroll\n// ---------------------------------------------------------------------------\n\nexport interface UseInfiniteScrollOptions {\n /** Only fire `onLoadMore` while true (a next page exists, none in flight). */\n enabled: boolean\n /** Scroll container the sentinel lives in. Defaults to the viewport. */\n root?: RefObject<HTMLElement | null>\n /** Prefetch distance before the sentinel is actually reached. */\n rootMargin?: string\n}\n\nfunction rethrowAsync(error: unknown) {\n queueMicrotask(() => {\n throw error\n })\n}\n\n/**\n * Fires `onLoadMore` when a sentinel element scrolls into view. Returns a ref\n * callback for that sentinel (typically the last element in a list).\n *\n * The observer is re-created whenever `enabled` flips, so a short first page\n * that leaves the sentinel on-screen keeps loading: when a load finishes and\n * `enabled` returns to true, the fresh observer re-reads the current\n * intersection state and fires again until the sentinel is pushed off-screen.\n */\nexport function useInfiniteScroll(\n onLoadMore: () => void,\n { enabled, root, rootMargin = '300px' }: UseInfiniteScrollOptions,\n): (node: HTMLElement | null) => void {\n const [sentinel, setSentinel] = useState<HTMLElement | null>(null)\n const sentinelRef = useCallback((node: HTMLElement | null) => setSentinel(node), [])\n const onLoadMoreRef = useRef(onLoadMore)\n\n useEffect(() => {\n onLoadMoreRef.current = onLoadMore\n }, [onLoadMore])\n\n useEffect(() => {\n if (!sentinel || !enabled) return\n if (typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n (entries) => {\n if (!entries.some((entry) => entry.isIntersecting)) return\n try {\n onLoadMoreRef.current()\n } catch (error) {\n rethrowAsync(error)\n }\n },\n { root: root?.current ?? null, rootMargin },\n )\n observer.observe(sentinel)\n return () => observer.disconnect()\n }, [sentinel, enabled, root, rootMargin])\n\n return sentinelRef\n}\n\n// ---------------------------------------------------------------------------\n// useSessionHistory — cursor-paged data over an injected port\n// ---------------------------------------------------------------------------\n\nexport interface SessionPageQuery {\n /** Trimmed search term; empty string means no filter. */\n q: string\n sort: SessionSort\n /** `null` for the first page. */\n cursor: string | null\n /** Aborted when the view changes or the component unmounts. */\n signal: AbortSignal\n}\n\n/** Data port — one page of sessions for the current view. */\nexport type FetchSessionPage = (query: SessionPageQuery) => Promise<SessionPage>\n\nexport interface UseSessionHistoryOptions {\n fetchPage: FetchSessionPage\n /** Trimmed search term driving the fetch. */\n q: string\n sort: SessionSort\n /** SSR page 1 of the default view, so the first paint costs no request. */\n initialPage: SessionPage\n /** The sort `initialPage` was rendered for. Default `'newest'`. */\n defaultSort?: SessionSort\n}\n\nexport interface SessionHistoryState {\n items: SessionSummary[]\n hasMore: boolean\n isLoadingFirst: boolean\n isLoadingMore: boolean\n isError: boolean\n loadMore: () => void\n /** Re-run whichever load failed. */\n retry: () => void\n /** Refetch page 1 — call after a client-side mutation (e.g. a delete). */\n reload: () => void\n}\n\n/** Cheap content signature so a consumer inlining `initialPage={{items}}` on\n * every render does not reseed (and re-render) forever. Identity alone — what\n * the per-product versions keyed on — makes that an infinite loop. */\nfunction seedSignature(page: SessionPage): string {\n return `${page.nextCursor ?? ''}|${page.items.map((item) => item.id).join(',')}`\n}\n\n/**\n * Infinite-scroll data source for the history view. Seeds from `initialPage`\n * for the default view (no fetch) and otherwise fetches page 1 for the current\n * search/sort; `loadMore` appends the next cursor page.\n *\n * Raw promises + `AbortController` rather than a router fetcher, so a filter\n * change cancels in-flight requests, pages accumulate, and a late response from\n * a superseded view is dropped by the monotonic `seq` guard.\n */\nexport function useSessionHistory({\n fetchPage,\n q,\n sort,\n initialPage,\n defaultSort = 'newest',\n}: UseSessionHistoryOptions): SessionHistoryState {\n const [items, setItems] = useState<SessionSummary[]>(initialPage.items)\n const [nextCursor, setNextCursor] = useState<string | null>(initialPage.nextCursor ?? null)\n const [phase, setPhase] = useState<'idle' | 'loadingFirst' | 'loadingMore' | 'error'>('idle')\n const [reloadKey, setReloadKey] = useState(0)\n\n const seqRef = useRef(0)\n const resetAbortRef = useRef<AbortController | null>(null)\n const loadMoreAbortRef = useRef<AbortController | null>(null)\n const loadingMoreRef = useRef(false)\n const lastOpRef = useRef<'first' | 'more'>('first')\n\n // Live values read inside the stable `loadMore` callback.\n const nextCursorRef = useRef(nextCursor)\n nextCursorRef.current = nextCursor\n const viewRef = useRef({ q, sort, fetchPage })\n viewRef.current = { q, sort, fetchPage }\n const seedRef = useRef(initialPage)\n seedRef.current = initialPage\n\n const isDefaultView = q === '' && sort === defaultSort\n const seedKey = useMemo(() => seedSignature(initialPage), [initialPage])\n\n // Reset on view change (q/sort), on a new SSR seed (loader revalidation), or\n // on an explicit retry/reload. The default view comes straight from SSR;\n // explicit reloads fetch page 1 so a client-only mutation refreshes this list\n // without waiting on a route loader.\n useEffect(() => {\n resetAbortRef.current?.abort()\n loadMoreAbortRef.current?.abort()\n loadingMoreRef.current = false\n const seq = ++seqRef.current\n\n if (isDefaultView && reloadKey === 0) {\n setItems(seedRef.current.items)\n setNextCursor(seedRef.current.nextCursor ?? null)\n setPhase('idle')\n return\n }\n\n const controller = new AbortController()\n resetAbortRef.current = controller\n lastOpRef.current = 'first'\n setItems([])\n setNextCursor(null)\n setPhase('loadingFirst')\n\n void (async () => {\n try {\n const page = await viewRef.current.fetchPage({ q, sort, cursor: null, signal: controller.signal })\n if (seq !== seqRef.current) return\n setItems(page.items)\n setNextCursor(page.nextCursor ?? null)\n setPhase('idle')\n } catch {\n if (controller.signal.aborted || seq !== seqRef.current) return\n setPhase('error')\n }\n })()\n\n return () => controller.abort()\n }, [q, sort, seedKey, isDefaultView, reloadKey])\n\n const loadMore = useCallback(() => {\n const cursor = nextCursorRef.current\n if (!cursor || loadingMoreRef.current) return\n\n const { q: currentQ, sort: currentSort, fetchPage: currentFetch } = viewRef.current\n const seq = seqRef.current\n loadingMoreRef.current = true\n lastOpRef.current = 'more'\n const controller = new AbortController()\n loadMoreAbortRef.current = controller\n setPhase('loadingMore')\n\n void (async () => {\n try {\n const page = await currentFetch({ q: currentQ, sort: currentSort, cursor, signal: controller.signal })\n if (seq !== seqRef.current) return\n setItems((prev) => mergeSessionPages(prev, page.items))\n setNextCursor(page.nextCursor ?? null)\n setPhase('idle')\n } catch {\n if (controller.signal.aborted || seq !== seqRef.current) return\n setPhase('error')\n } finally {\n if (seq === seqRef.current) loadingMoreRef.current = false\n }\n })()\n }, [])\n\n const retry = useCallback(() => {\n if (lastOpRef.current === 'more') loadMore()\n else setReloadKey((key) => key + 1)\n }, [loadMore])\n\n const reload = useCallback(() => {\n setReloadKey((key) => key + 1)\n }, [])\n\n useEffect(\n () => () => {\n resetAbortRef.current?.abort()\n loadMoreAbortRef.current?.abort()\n },\n [],\n )\n\n return {\n items,\n hasMore: nextCursor !== null,\n isLoadingFirst: phase === 'loadingFirst',\n isLoadingMore: phase === 'loadingMore',\n isError: phase === 'error',\n loadMore,\n retry,\n reload,\n }\n}\n\n// ---------------------------------------------------------------------------\n// useSessionActions — rename / delete over injected mutations\n// ---------------------------------------------------------------------------\n\nexport interface SessionActionsOptions {\n /** Persist a new title. Reject to surface the error in the dialog. */\n renameSession: (sessionId: string, title: string) => Promise<void>\n deleteSession: (sessionId: string) => Promise<void>\n /** Called after a successful rename/delete — revalidate the rail here. */\n onChanged?: () => void\n /** Called after deleting the session the user is currently viewing, so the\n * product can navigate away from a route that no longer resolves. */\n onDeletedCurrent?: () => void\n /** The open session, compared against the delete target. */\n currentSessionId?: string | null\n /** Product toast/log seam. Errors also render inside the dialog. */\n notify?: (level: 'success' | 'error', message: string) => void\n labels?: Partial<SessionActionLabels>\n}\n\nexport interface SessionActionLabels {\n renameTitle: string\n renameField: string\n renameSubmit: string\n deleteTitle: string\n deleteBody: (title: string) => string\n deleteSubmit: string\n cancel: string\n renamed: string\n deleted: string\n renameFailed: string\n deleteFailed: string\n}\n\nconst DEFAULT_LABELS: SessionActionLabels = {\n renameTitle: 'Rename session',\n renameField: 'Title',\n renameSubmit: 'Save',\n deleteTitle: 'Delete session?',\n deleteBody: (title) => `This will permanently delete “${title}” and its messages. This cannot be undone.`,\n deleteSubmit: 'Delete',\n cancel: 'Cancel',\n renamed: 'Session renamed',\n deleted: 'Session deleted',\n renameFailed: 'Failed to rename session',\n deleteFailed: 'Failed to delete session',\n}\n\nexport interface SessionActions {\n openRename: (session: SessionSummary) => void\n openDelete: (session: SessionSummary) => void\n /** Render once, anywhere that survives navigation (the layout). */\n dialogs: ReactNode\n busy: boolean\n}\n\n/**\n * Rename + delete for one session, shared by the rail kebab and the history\n * row menu so both drive the same dialogs and the same product mutations.\n *\n * Dialogs are owned here rather than returned as raw state: two surfaces\n * needing the same confirm step is exactly how a product ends up with two\n * subtly different delete confirmations.\n */\nexport function useSessionActions({\n renameSession,\n deleteSession,\n onChanged,\n onDeletedCurrent,\n currentSessionId,\n notify,\n labels,\n}: SessionActionsOptions): SessionActions {\n const text = { ...DEFAULT_LABELS, ...labels }\n const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null)\n const [renameValue, setRenameValue] = useState('')\n const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const openRename = useCallback((session: SessionSummary) => {\n setError(null)\n setRenameTarget(session)\n setRenameValue(session.title ?? '')\n }, [])\n\n const openDelete = useCallback((session: SessionSummary) => {\n setError(null)\n setDeleteTarget(session)\n }, [])\n\n const submitRename = useCallback(async () => {\n if (!renameTarget) return\n const title = renameValue.trim()\n // A no-op rename closes rather than writing — otherwise every accidental\n // open costs a request and a revalidation.\n if (!title || title === renameTarget.title) {\n setRenameTarget(null)\n return\n }\n setBusy(true)\n setError(null)\n try {\n await renameSession(renameTarget.id, title)\n setRenameTarget(null)\n notify?.('success', text.renamed)\n onChanged?.()\n } catch (e) {\n const message = e instanceof Error ? e.message : text.renameFailed\n setError(message)\n notify?.('error', message)\n } finally {\n setBusy(false)\n }\n }, [renameTarget, renameValue, renameSession, notify, onChanged, text.renamed, text.renameFailed])\n\n const confirmDelete = useCallback(async () => {\n if (!deleteTarget) return\n const deletingCurrent = currentSessionId != null && deleteTarget.id === currentSessionId\n setBusy(true)\n setError(null)\n try {\n await deleteSession(deleteTarget.id)\n setDeleteTarget(null)\n notify?.('success', text.deleted)\n onChanged?.()\n if (deletingCurrent) onDeletedCurrent?.()\n } catch (e) {\n const message = e instanceof Error ? e.message : text.deleteFailed\n setError(message)\n notify?.('error', message)\n } finally {\n setBusy(false)\n }\n }, [deleteTarget, currentSessionId, deleteSession, notify, onChanged, onDeletedCurrent, text.deleted, text.deleteFailed])\n\n const dialogs = (\n <>\n {renameTarget && (\n <SessionDialog\n title={text.renameTitle}\n onClose={() => setRenameTarget(null)}\n busy={busy}\n error={error}\n footer={\n <>\n <DialogButton onClick={() => setRenameTarget(null)} disabled={busy} variant=\"ghost\">\n {text.cancel}\n </DialogButton>\n <DialogButton onClick={() => void submitRename()} disabled={busy || !renameValue.trim()}>\n {text.renameSubmit}\n </DialogButton>\n </>\n }\n >\n <label htmlFor=\"agent-app-rename-session\" className=\"text-xs text-muted-foreground\">\n {text.renameField}\n </label>\n <input\n id=\"agent-app-rename-session\"\n value={renameValue}\n autoFocus\n onChange={(e) => setRenameValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !busy) {\n e.preventDefault()\n void submitRename()\n }\n }}\n className=\"mt-1.5 h-9 w-full rounded-md border border-strong bg-background px-3 text-sm text-foreground\"\n />\n </SessionDialog>\n )}\n\n {deleteTarget && (\n <SessionDialog\n title={text.deleteTitle}\n onClose={() => setDeleteTarget(null)}\n busy={busy}\n error={error}\n footer={\n <>\n <DialogButton onClick={() => setDeleteTarget(null)} disabled={busy} variant=\"ghost\">\n {text.cancel}\n </DialogButton>\n <DialogButton onClick={() => void confirmDelete()} disabled={busy} variant=\"destructive\">\n {text.deleteSubmit}\n </DialogButton>\n </>\n }\n >\n <p className=\"text-sm text-muted-foreground\">{text.deleteBody(sessionLabel(deleteTarget))}</p>\n </SessionDialog>\n )}\n </>\n )\n\n return { openRename, openDelete, dialogs, busy }\n}\n\nfunction DialogButton({\n children,\n onClick,\n disabled,\n variant = 'primary',\n}: {\n children: ReactNode\n onClick: () => void\n disabled?: boolean\n variant?: 'primary' | 'ghost' | 'destructive'\n}) {\n const tone =\n variant === 'ghost'\n ? 'text-muted-foreground hover:bg-accent hover:text-foreground'\n : variant === 'destructive'\n ? 'bg-destructive text-destructive-foreground hover:opacity-90'\n : 'bg-primary text-primary-foreground hover:opacity-90'\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n className={`h-9 rounded-md px-3 text-sm font-medium transition disabled:opacity-50 ${tone}`}\n >\n {children}\n </button>\n )\n}\n\nfunction SessionDialog({\n title,\n children,\n footer,\n onClose,\n busy,\n error,\n}: {\n title: string\n children: ReactNode\n footer: ReactNode\n onClose: () => void\n busy: boolean\n error: string | null\n}) {\n // Escape closes unless a mutation is in flight — closing mid-write would hide\n // the error the user needs to see.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && !busy) onClose()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n }, [busy, onClose])\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-black/50\"\n onClick={() => {\n if (!busy) onClose()\n }}\n aria-hidden\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n className={`relative w-full max-w-sm rounded-xl border border-card-edge bg-popover p-5 ${OVERLAY_SHADOW}`}\n >\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n <div className=\"mt-3\">{children}</div>\n {error && (\n <p role=\"alert\" className=\"mt-3 rounded-md bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {error}\n </p>\n )}\n <div className=\"mt-5 flex justify-end gap-2\">{footer}</div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SessionHistoryPanel\n// ---------------------------------------------------------------------------\n\nexport interface SessionHistoryPanelProps {\n history: SessionHistoryState\n /** Whether the workspace has any sessions at all — decided by the SSR page,\n * independent of the active search, so filtering to zero shows \"no matches\"\n * rather than the first-run empty state. */\n hasAnySessions: boolean\n query: string\n onQueryChange: (value: string) => void\n sort: SessionSort\n onSortChange: (value: SessionSort) => void\n /** Product route for one session row. */\n hrefForSession: (sessionId: string) => string\n /** Rendered as the row link. Defaults to an `<a>`; pass a router Link to keep\n * client-side navigation. */\n linkComponent?: LinkLikeComponent\n /** Ids currently mid-turn — renders the responding treatment. */\n respondingSessionIds?: ReadonlySet<string>\n onRename?: (session: SessionSummary) => void\n onDelete?: (session: SessionSummary) => void\n /** Product-owned mutation for selected rows or a workspace-wide age range. */\n onBulkAction?: (action: SessionBulkAction) => Promise<void>\n /** Menu wording, so this surface and the rail name the same act the same way\n * — a product whose delete is really an archive says so in both places. */\n renameLabel?: string\n deleteLabel?: string\n /**\n * Row actions this shell has no opinion about — pin, categorise, share.\n * Same seam and same ordering as the rail's `SessionRowActions.extraActions`:\n * evaluated per session, placed between rename and delete. This menu is\n * text-only, so `icon` is ignored here and honoured on the rail.\n */\n extraActions?: (session: SessionSummary) => SessionRailAction[]\n /** New-session destination for the header action. Omitted ⇒ no button. */\n newSessionHref?: string\n title?: string\n untitledLabel?: string\n emptyTitle?: string\n emptyDescription?: string\n /** Absolute → relative timestamp. Defaults to a compact built-in. */\n formatTimestamp?: (isoDate: string | null) => string\n /** Max width of the reading column. `'full'` opts out for a product whose\n * surface really is a wide table. Default keeps title and timestamp inside\n * one scannable line rather than at opposite edges of a 1440px viewport. */\n contentWidth?: 'reading' | 'full'\n className?: string\n}\n\nexport type SessionBulkAction =\n | { kind: 'selected'; ids: string[] }\n | { kind: 'older-than'; days: number }\n | { kind: 'newer-than'; days: number }\n\nexport interface LinkLikeProps {\n to: string\n className?: string\n children?: ReactNode\n}\n\nexport type LinkLikeComponent = (props: LinkLikeProps) => ReactNode\n\nfunction AnchorLink({ to, className, children }: LinkLikeProps) {\n return (\n <a href={to} className={className}>\n {children}\n </a>\n )\n}\n\nconst MINUTE = 60_000\nconst HOUR = 60 * MINUTE\nconst DAY = 24 * HOUR\n\n/** Compact relative time. Overridable — a product with its own i18n passes\n * `formatTimestamp` rather than this being the only option. */\nexport function formatSessionTimestamp(isoDate: string | null): string {\n if (!isoDate) return ''\n const at = Date.parse(isoDate)\n if (Number.isNaN(at)) return ''\n const delta = Date.now() - at\n if (delta < MINUTE) return 'just now'\n if (delta < HOUR) return `${Math.floor(delta / MINUTE)}m ago`\n if (delta < DAY) return `${Math.floor(delta / HOUR)}h ago`\n if (delta < 7 * DAY) return `${Math.floor(delta / DAY)}d ago`\n return new Date(at).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction SkeletonRows() {\n // The shimmer rows stay hidden from assistive tech — they say nothing a\n // reader can use — but the wait itself has to be announced, or the panel is\n // silent from the first paint until the rows arrive. The live region is a\n // sibling so the rows' own flex layout is untouched.\n return (\n <>\n <span role=\"status\" aria-live=\"polite\" aria-busy={true} className=\"sr-only\">\n Loading sessions…\n </span>\n <div className=\"flex flex-col gap-0.5\" aria-hidden>\n {Array.from({ length: 8 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3 px-3 py-2.5\">\n <div className=\"h-4 w-4 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-1/2 animate-pulse rounded bg-muted\" />\n <div className=\"ml-auto h-3 w-12 animate-pulse rounded bg-muted\" />\n </div>\n ))}\n </div>\n </>\n )\n}\n\nfunction MessageIcon({ className }: { className?: string }) {\n return (\n <svg viewBox=\"0 0 24 24\" className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n )\n}\n\n/**\n * The full session history: search, sort, cursor-paged rows with per-row\n * actions, and the states in between (first-run empty, loading, no matches,\n * error + retry).\n *\n * This is the surface the rail's capped list overflows into — the reason the\n * rail can stay short without hiding the user's work.\n */\nexport function SessionHistoryPanel({\n history,\n hasAnySessions,\n query,\n onQueryChange,\n sort,\n onSortChange,\n hrefForSession,\n linkComponent: Link = AnchorLink,\n respondingSessionIds,\n onRename,\n onDelete,\n onBulkAction,\n renameLabel = 'Rename',\n deleteLabel = 'Delete',\n extraActions,\n newSessionHref,\n title = 'History',\n untitledLabel = UNTITLED_SESSION_LABEL,\n emptyTitle = 'No sessions yet',\n emptyDescription = 'Your chat sessions will show up here once you start one.',\n formatTimestamp = formatSessionTimestamp,\n contentWidth = 'reading',\n className,\n}: SessionHistoryPanelProps) {\n const scrollRef = useRef<HTMLDivElement | null>(null)\n const sentinelRef = useInfiniteScroll(history.loadMore, {\n enabled: history.hasMore && !history.isLoadingMore && !history.isError,\n root: scrollRef,\n rootMargin: '300px',\n })\n const searchTerm = query.trim()\n const isSearching = searchTerm.length > 0\n const column = contentWidth === 'full' ? 'w-full' : 'mx-auto w-full max-w-4xl'\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())\n const [ageDays, setAgeDays] = useState('30')\n const [bulkTarget, setBulkTarget] = useState<{\n action: SessionBulkAction\n title: string\n body: string\n } | null>(null)\n const [bulkBusy, setBulkBusy] = useState(false)\n const [bulkError, setBulkError] = useState<string | null>(null)\n\n useEffect(() => {\n setSelectedIds(new Set())\n }, [searchTerm, sort])\n\n useEffect(() => {\n const visible = new Set(history.items.map((item) => item.id))\n setSelectedIds((current) => {\n const next = new Set([...current].filter((id) => visible.has(id)))\n return next.size === current.size ? current : next\n })\n }, [history.items])\n\n const selectedCount = selectedIds.size\n const allVisibleSelected = history.items.length > 0 && history.items.every((item) => selectedIds.has(item.id))\n const parsedAgeDays = Number(ageDays)\n const validAgeDays = Number.isInteger(parsedAgeDays) && parsedAgeDays >= 1 && parsedAgeDays <= 36_500\n\n const openBulkAction = useCallback((action: SessionBulkAction) => {\n const verb = deleteLabel.toLowerCase()\n if (action.kind === 'selected') {\n setBulkTarget({\n action,\n title: `${deleteLabel} selected sessions?`,\n body: `${verb === 'delete' ? 'This permanently removes' : `This ${verb}s`} ${action.ids.length} selected session${action.ids.length === 1 ? '' : 's'} and its messages.`,\n })\n return\n }\n const range = action.kind === 'older-than' ? `older than ${action.days} days` : `from the last ${action.days} days`\n setBulkTarget({\n action,\n title: `${deleteLabel} sessions ${range}?`,\n body: 'This applies to every matching session in this workspace, including sessions not currently loaded in this list.',\n })\n }, [deleteLabel])\n\n const confirmBulkAction = useCallback(async () => {\n if (!bulkTarget || !onBulkAction) return\n setBulkBusy(true)\n setBulkError(null)\n try {\n await onBulkAction(bulkTarget.action)\n setBulkTarget(null)\n setSelectedIds(new Set())\n history.reload()\n } catch (error) {\n setBulkError(error instanceof Error ? error.message : `Could not ${deleteLabel.toLowerCase()} sessions`)\n } finally {\n setBulkBusy(false)\n }\n }, [bulkTarget, deleteLabel, history, onBulkAction])\n\n return (\n <div className={`flex min-h-0 min-w-0 flex-1 flex-col ${className ?? ''}`}>\n {/* 56px, matching the rail header the fleet aligned on. */}\n <header className=\"flex h-14 shrink-0 items-center border-b border-border px-4 sm:px-6\">\n <div className={`flex items-center gap-3 px-3 ${column}`}>\n <h1 className=\"flex-1 truncate text-sm font-semibold text-foreground\">{title}</h1>\n {newSessionHref && (\n <Link\n to={newSessionHref}\n className=\"inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground transition hover:opacity-90\"\n >\n <span aria-hidden className=\"text-sm leading-none\">+</span>\n New chat\n </Link>\n )}\n </div>\n </header>\n\n <div ref={scrollRef} className=\"min-h-0 flex-1 overflow-y-auto\">\n {hasAnySessions && (\n <div className=\"sticky top-0 z-10 bg-background px-4 sm:px-6\">\n <div className={`flex flex-col gap-2 px-3 pb-3 pt-4 sm:flex-row sm:items-center sm:gap-3 ${column}`}>\n <input\n type=\"search\"\n value={query}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder=\"Search your sessions…\"\n aria-label=\"Search sessions\"\n className=\"h-9 min-w-0 appearance-none rounded-md border border-strong bg-card px-3 text-sm text-foreground placeholder:text-muted-foreground sm:flex-1 [&::-webkit-search-cancel-button]:appearance-none\"\n />\n <select\n value={sort}\n onChange={(e) => onSortChange(e.target.value as SessionSort)}\n aria-label=\"Sort sessions\"\n className=\"h-9 shrink-0 appearance-none rounded-md border border-strong bg-card px-2 text-sm text-foreground sm:w-[132px]\"\n >\n <option value=\"newest\">Newest</option>\n <option value=\"oldest\">Oldest</option>\n </select>\n </div>\n {onBulkAction && (\n <div className={`flex flex-col gap-2 border-t border-border px-3 py-3 ${column}`}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setSelectedIds(new Set(history.items.map((item) => item.id)))}\n disabled={allVisibleSelected || history.items.length === 0}\n className=\"h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50\"\n >\n Select all\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedIds(new Set())}\n disabled={selectedCount === 0}\n className=\"h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50\"\n >\n Deselect all\n </button>\n <span className=\"text-xs text-muted-foreground\" aria-live=\"polite\">\n {selectedCount} selected\n </span>\n {selectedCount > 0 && (\n <button\n type=\"button\"\n onClick={() => openBulkAction({ kind: 'selected', ids: [...selectedIds] })}\n className=\"h-8 rounded-md bg-destructive px-2.5 text-xs font-medium text-destructive-foreground transition hover:opacity-90\"\n >\n {deleteLabel} selected\n </button>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <label htmlFor=\"agent-app-session-age\" className=\"text-xs text-muted-foreground\">\n Session age\n </label>\n <input\n id=\"agent-app-session-age\"\n type=\"number\"\n min={1}\n max={36_500}\n value={ageDays}\n onChange={(event) => setAgeDays(event.target.value)}\n aria-invalid={ageDays.length > 0 && !validAgeDays}\n className=\"h-8 w-20 rounded-md border border-strong bg-card px-2 text-xs tabular-nums text-foreground\"\n />\n <span className=\"text-xs text-muted-foreground\">days</span>\n <button\n type=\"button\"\n onClick={() => openBulkAction({ kind: 'older-than', days: parsedAgeDays })}\n disabled={!validAgeDays}\n className=\"h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50\"\n >\n {deleteLabel} older\n </button>\n <button\n type=\"button\"\n onClick={() => openBulkAction({ kind: 'newer-than', days: parsedAgeDays })}\n disabled={!validAgeDays}\n className=\"h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50\"\n >\n {deleteLabel} recent\n </button>\n </div>\n </div>\n )}\n </div>\n )}\n\n <div className={`px-4 pb-8 pt-1 sm:px-6 ${column}`}>\n {!hasAnySessions ? (\n <div className=\"flex min-h-[50vh] flex-col items-center justify-center gap-2 text-center\">\n <p className=\"text-sm font-medium text-foreground\">{emptyTitle}</p>\n <p className=\"max-w-xs text-xs text-muted-foreground\">{emptyDescription}</p>\n </div>\n ) : history.isLoadingFirst ? (\n <SkeletonRows />\n ) : history.items.length === 0 ? (\n history.isError ? (\n <ErrorBlock onRetry={history.retry} message=\"Couldn’t load your sessions.\" />\n ) : isSearching ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n No sessions match “{searchTerm}”.\n </p>\n ) : (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">No sessions remain.</p>\n )\n ) : (\n <div className=\"flex flex-col gap-0.5\">\n {history.items.map((session) => (\n <SessionRow\n key={session.id}\n session={session}\n href={hrefForSession(session.id)}\n Link={Link}\n responding={respondingSessionIds?.has(session.id) ?? false}\n untitledLabel={untitledLabel}\n timestamp={formatTimestamp(session.updatedAt)}\n onRename={onRename}\n onDelete={onDelete}\n selectable={Boolean(onBulkAction)}\n selected={selectedIds.has(session.id)}\n onSelectedChange={(selected) => {\n setSelectedIds((current) => {\n const next = new Set(current)\n if (selected) next.add(session.id)\n else next.delete(session.id)\n return next\n })\n }}\n renameLabel={renameLabel}\n deleteLabel={deleteLabel}\n extraActions={extraActions}\n />\n ))}\n\n {history.isError ? (\n <ErrorBlock onRetry={history.retry} message=\"Couldn’t load more sessions.\" inline />\n ) : history.hasMore ? (\n <div ref={sentinelRef} className=\"flex items-center justify-center py-6\">\n {history.isLoadingMore && (\n <span role=\"status\" aria-live=\"polite\" aria-busy={true} className=\"text-xs text-muted-foreground\">\n Loading…\n </span>\n )}\n </div>\n ) : null}\n </div>\n )}\n </div>\n </div>\n {bulkTarget && (\n <SessionDialog\n title={bulkTarget.title}\n onClose={() => {\n if (!bulkBusy) {\n setBulkTarget(null)\n setBulkError(null)\n }\n }}\n busy={bulkBusy}\n error={bulkError}\n footer={\n <>\n <DialogButton\n onClick={() => {\n setBulkTarget(null)\n setBulkError(null)\n }}\n disabled={bulkBusy}\n variant=\"ghost\"\n >\n Cancel\n </DialogButton>\n <DialogButton onClick={() => void confirmBulkAction()} disabled={bulkBusy} variant=\"destructive\">\n {bulkBusy ? 'Working…' : deleteLabel}\n </DialogButton>\n </>\n }\n >\n <p className=\"text-sm text-muted-foreground\">{bulkTarget.body}</p>\n </SessionDialog>\n )}\n </div>\n )\n}\n\nfunction ErrorBlock({ message, onRetry, inline }: { message: string; onRetry: () => void; inline?: boolean }) {\n return (\n <div\n className={\n inline\n ? 'flex items-center justify-center gap-3 py-6 text-sm text-muted-foreground'\n : 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center'\n }\n >\n <span className=\"text-sm text-muted-foreground\">{message}</span>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent\"\n >\n Retry\n </button>\n </div>\n )\n}\n\nfunction SessionRow({\n session,\n href,\n Link,\n responding,\n untitledLabel,\n timestamp,\n onRename,\n onDelete,\n renameLabel,\n deleteLabel,\n extraActions,\n selectable,\n selected,\n onSelectedChange,\n}: {\n session: SessionSummary\n href: string\n Link: LinkLikeComponent\n responding: boolean\n untitledLabel: string\n timestamp: string\n onRename?: (session: SessionSummary) => void\n onDelete?: (session: SessionSummary) => void\n renameLabel: string\n deleteLabel: string\n extraActions?: (session: SessionSummary) => SessionRailAction[]\n selectable: boolean\n selected: boolean\n onSelectedChange: (selected: boolean) => void\n}) {\n const [menuOpen, setMenuOpen] = useState(false)\n const panelId = useId()\n const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(menuOpen, setMenuOpen)\n const extras = extraActions?.(session) ?? []\n const hasMenu = Boolean(onRename) || Boolean(onDelete) || extras.length > 0\n // An unread dot next to a live responding indicator is two signals for one\n // state; the working indicator wins while the turn runs.\n const showUnread = Boolean(session.unread) && !responding\n\n return (\n <div className={`group relative flex items-center gap-2 rounded-lg px-3 py-2.5 transition-colors hover:bg-accent ${selected ? 'bg-primary/10' : ''}`}>\n {selectable && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onSelectedChange(event.target.checked)}\n aria-label={`Select ${sessionLabel(session, untitledLabel)}`}\n className=\"h-4 w-4 shrink-0 rounded border-border accent-primary\"\n />\n )}\n <Link to={href} className=\"flex min-w-0 flex-1 items-center gap-3\">\n {showUnread && <span className=\"h-1.5 w-1.5 shrink-0 rounded-full bg-primary\" aria-hidden />}\n <MessageIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span\n className={`truncate text-sm ${responding ? 'text-muted-foreground' : 'text-foreground'} ${showUnread ? 'font-semibold' : ''}`}\n {...(responding ? { role: 'status', 'aria-label': 'Agent responding' } : {})}\n >\n {sessionLabel(session, untitledLabel)}\n </span>\n </Link>\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">{timestamp}</span>\n {hasMenu && (\n <div ref={containerRef} className=\"relative shrink-0\">\n <button\n type=\"button\"\n {...triggerProps}\n aria-label=\"Session actions\"\n aria-controls={menuOpen ? panelId : undefined}\n onClick={() => setMenuOpen((open) => !open)}\n // Visible by default and hover-revealed only from `sm:` up: a\n // touch device has no hover, so an opacity-0 kebab is an action\n // the user can never reach.\n className=\"flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted hover:text-foreground focus-visible:opacity-100 sm:opacity-0 sm:group-hover:opacity-100 aria-expanded:opacity-100\"\n >\n <span aria-hidden className=\"text-base leading-none\">⋯</span>\n </button>\n <PopoverSurface\n open={menuOpen}\n id={panelId}\n role=\"menu\"\n triggerRef={triggerRef}\n panelRef={panelRef}\n className={`w-36 overflow-hidden rounded-md border border-card-edge bg-popover py-1 ${OVERLAY_SHADOW}`}\n >\n {onRename && (\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setMenuOpen(false)\n onRename(session)\n }}\n className=\"block w-full px-3 py-1.5 text-left text-xs text-foreground transition hover:bg-accent\"\n >\n {renameLabel}\n </button>\n )}\n {extras.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setMenuOpen(false)\n action.onSelect()\n }}\n className={`block w-full px-3 py-1.5 text-left text-xs transition ${\n action.destructive\n ? 'text-destructive hover:bg-destructive/10'\n : 'text-foreground hover:bg-accent'\n }`}\n >\n {action.label}\n </button>\n ))}\n {onDelete && (\n <button\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setMenuOpen(false)\n onDelete(session)\n }}\n className=\"block w-full px-3 py-1.5 text-left text-xs text-destructive transition hover:bg-destructive/10\"\n >\n {deleteLabel}\n </button>\n )}\n </PopoverSurface>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -174,8 +174,9 @@ function optimisticGeneration({
|
|
|
174
174
|
clientRequestId,
|
|
175
175
|
outputIndex,
|
|
176
176
|
outputCount
|
|
177
|
-
}) {
|
|
177
|
+
}, aspectRatio) {
|
|
178
178
|
const batchId = outputIndex == null ? void 0 : clientRequestId;
|
|
179
|
+
const aspectRatioMetadata = Number.isFinite(aspectRatio) && (aspectRatio ?? 0) > 0 ? { aspectRatio } : {};
|
|
179
180
|
return {
|
|
180
181
|
id: outputIndex == null ? `local-${clientRequestId}` : `local-${clientRequestId}-${outputIndex}`,
|
|
181
182
|
type,
|
|
@@ -190,7 +191,8 @@ function optimisticGeneration({
|
|
|
190
191
|
clientRequestId,
|
|
191
192
|
batchId,
|
|
192
193
|
outputIndex,
|
|
193
|
-
outputCount
|
|
194
|
+
outputCount,
|
|
195
|
+
...aspectRatioMetadata
|
|
194
196
|
}
|
|
195
197
|
};
|
|
196
198
|
}
|
|
@@ -209,6 +211,96 @@ function normalizeImageCount(value) {
|
|
|
209
211
|
if (!Number.isFinite(numeric)) return MIN_IMAGE_COUNT;
|
|
210
212
|
return Math.min(Math.max(Math.trunc(numeric), MIN_IMAGE_COUNT), MAX_IMAGE_COUNT);
|
|
211
213
|
}
|
|
214
|
+
function generationBatchKey(generation) {
|
|
215
|
+
const metadata = generation.metadata ?? {};
|
|
216
|
+
if (typeof metadata.batchId === "string" && metadata.batchId.trim()) return metadata.batchId;
|
|
217
|
+
if (typeof metadata.clientRequestId === "string" && metadata.clientRequestId.trim()) return metadata.clientRequestId;
|
|
218
|
+
return generation.id;
|
|
219
|
+
}
|
|
220
|
+
function generationAssetId(generation) {
|
|
221
|
+
const value = generation.metadata?.assetId;
|
|
222
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
223
|
+
}
|
|
224
|
+
function generationsInBatch(generations, batchKey) {
|
|
225
|
+
return generations.map((generation, inputIndex) => ({ generation, inputIndex })).filter(({ generation }) => generationBatchKey(generation) === batchKey).sort((left, right) => {
|
|
226
|
+
const leftIndex = left.generation.metadata?.outputIndex;
|
|
227
|
+
const rightIndex = right.generation.metadata?.outputIndex;
|
|
228
|
+
const leftOrder = typeof leftIndex === "number" && Number.isFinite(leftIndex) ? leftIndex : Infinity;
|
|
229
|
+
const rightOrder = typeof rightIndex === "number" && Number.isFinite(rightIndex) ? rightIndex : Infinity;
|
|
230
|
+
return leftOrder - rightOrder || left.inputIndex - right.inputIndex;
|
|
231
|
+
}).map(({ generation }) => generation);
|
|
232
|
+
}
|
|
233
|
+
function ratioFromDimensions(value, separator) {
|
|
234
|
+
const match = separator === "size" ? /^(\d+)[x×](\d+)$/.exec(value) : /^(\d+):(\d+)$/.exec(value);
|
|
235
|
+
if (!match) return void 0;
|
|
236
|
+
const width = Number(match[1]);
|
|
237
|
+
const height = Number(match[2]);
|
|
238
|
+
return width > 0 && height > 0 ? width / height : void 0;
|
|
239
|
+
}
|
|
240
|
+
function roundedRatio(value) {
|
|
241
|
+
return +value.toFixed(4);
|
|
242
|
+
}
|
|
243
|
+
function generationAspectRatio(generation) {
|
|
244
|
+
const metadata = generation.metadata ?? {};
|
|
245
|
+
if (typeof metadata.aspectRatio === "number" && Number.isFinite(metadata.aspectRatio) && metadata.aspectRatio > 0) {
|
|
246
|
+
return roundedRatio(metadata.aspectRatio);
|
|
247
|
+
}
|
|
248
|
+
if (typeof metadata.size === "string") {
|
|
249
|
+
const ratio = ratioFromDimensions(metadata.size, "size");
|
|
250
|
+
if (ratio !== void 0) return roundedRatio(ratio);
|
|
251
|
+
}
|
|
252
|
+
if (typeof metadata.aspectRatio === "string") {
|
|
253
|
+
const ratio = ratioFromDimensions(metadata.aspectRatio, "aspect");
|
|
254
|
+
if (ratio !== void 0) return roundedRatio(ratio);
|
|
255
|
+
}
|
|
256
|
+
if (generation.type === "video") return roundedRatio(16 / 9);
|
|
257
|
+
if (generation.type === "speech" || generation.type === "audio") return 3.2;
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
function aspectRatioFromOptions(type, options) {
|
|
261
|
+
if (type === "speech") return 3.2;
|
|
262
|
+
const ratio = type === "image" ? options.size ? ratioFromDimensions(options.size, "size") : void 0 : type === "video" ? options.aspectRatio ? ratioFromDimensions(options.aspectRatio, "aspect") : void 0 : void 0;
|
|
263
|
+
return ratio === void 0 ? void 0 : roundedRatio(ratio);
|
|
264
|
+
}
|
|
265
|
+
function defaultVaultPathFor(generations) {
|
|
266
|
+
const types = new Set(generations.map((generation) => generation.type));
|
|
267
|
+
if (types.size !== 1) return "generated/media";
|
|
268
|
+
const [type] = types;
|
|
269
|
+
return type !== void 0 && isGenerationType(type) ? outputPathFor(type) : "generated/media";
|
|
270
|
+
}
|
|
271
|
+
function normalizeVaultPath(input) {
|
|
272
|
+
const path = input.trim().replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
|
|
273
|
+
if (!path) return null;
|
|
274
|
+
const segments = path.split("/");
|
|
275
|
+
return segments.some((segment) => segment === "." || segment === ".." || segment.includes("\\")) ? null : path;
|
|
276
|
+
}
|
|
277
|
+
function mergeGenerationPages(prev, next) {
|
|
278
|
+
const seen = new Set(prev.map((generation) => generation.id));
|
|
279
|
+
const merged = [...prev];
|
|
280
|
+
for (const generation of next) {
|
|
281
|
+
if (seen.has(generation.id)) continue;
|
|
282
|
+
seen.add(generation.id);
|
|
283
|
+
merged.push(generation);
|
|
284
|
+
}
|
|
285
|
+
return merged;
|
|
286
|
+
}
|
|
287
|
+
function generationSpecSegments(generation) {
|
|
288
|
+
const metadata = generation.metadata ?? {};
|
|
289
|
+
const segments = [];
|
|
290
|
+
if (typeof metadata.size === "string") segments.push(metadata.size.replace(/x/g, "\xD7"));
|
|
291
|
+
if (typeof metadata.resolution === "string") segments.push(metadata.resolution);
|
|
292
|
+
if (typeof metadata.aspectRatio === "string" && /^(\d+):(\d+)$/.test(metadata.aspectRatio)) {
|
|
293
|
+
segments.push(metadata.aspectRatio);
|
|
294
|
+
}
|
|
295
|
+
if (typeof metadata.duration === "string") {
|
|
296
|
+
segments.push(metadata.duration);
|
|
297
|
+
} else if (typeof metadata.durationSeconds === "number" && Number.isFinite(metadata.durationSeconds)) {
|
|
298
|
+
const seconds = Math.max(0, Math.floor(metadata.durationSeconds));
|
|
299
|
+
segments.push(`${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`);
|
|
300
|
+
}
|
|
301
|
+
if (typeof metadata.voice === "string") segments.push(metadata.voice);
|
|
302
|
+
return segments;
|
|
303
|
+
}
|
|
212
304
|
|
|
213
305
|
// src/studio/model-options.ts
|
|
214
306
|
var SEEDANCE_2_0 = {
|
|
@@ -402,6 +494,46 @@ function reconcileOptionValues(options, current, opts) {
|
|
|
402
494
|
return reconciled;
|
|
403
495
|
}
|
|
404
496
|
|
|
497
|
+
// src/studio/ports.ts
|
|
498
|
+
var MEDIA_TYPE_FILTERS = [
|
|
499
|
+
{ value: "all", label: "All media" },
|
|
500
|
+
{ value: "image", label: "Images" },
|
|
501
|
+
{ value: "video", label: "Videos" },
|
|
502
|
+
{ value: "speech", label: "Audio" }
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
// src/studio/audio-preview.ts
|
|
506
|
+
var GRID_WAVEFORM_BARS = 26;
|
|
507
|
+
var WIDE_WAVEFORM_BARS = 72;
|
|
508
|
+
function hashSeed(value) {
|
|
509
|
+
let hash = 2166136261;
|
|
510
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
511
|
+
hash ^= value.charCodeAt(index);
|
|
512
|
+
hash = Math.imul(hash, 16777619);
|
|
513
|
+
}
|
|
514
|
+
return hash >>> 0;
|
|
515
|
+
}
|
|
516
|
+
function mulberry32(seed) {
|
|
517
|
+
let a = seed;
|
|
518
|
+
return () => {
|
|
519
|
+
a |= 0;
|
|
520
|
+
a = a + 1831565813 | 0;
|
|
521
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
522
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
523
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function previewWaveformBars(seed, count) {
|
|
527
|
+
const rnd = mulberry32(hashSeed(seed));
|
|
528
|
+
return Array.from({ length: count }, (_, index) => {
|
|
529
|
+
const t = count > 1 ? index / (count - 1) : 0;
|
|
530
|
+
const env = Math.sin(Math.PI * t) * 0.55 + 0.45;
|
|
531
|
+
const heightPct = +Math.max(7, (0.22 + rnd() * 0.78) * env * 92).toFixed(1);
|
|
532
|
+
const opacity = +(0.5 + rnd() * 0.5).toFixed(2);
|
|
533
|
+
return { heightPct, opacity };
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
405
537
|
export {
|
|
406
538
|
GENERATION_TYPES,
|
|
407
539
|
isGenerationType,
|
|
@@ -425,6 +557,15 @@ export {
|
|
|
425
557
|
optimisticGeneration,
|
|
426
558
|
failedOptimisticGeneration,
|
|
427
559
|
normalizeImageCount,
|
|
560
|
+
generationBatchKey,
|
|
561
|
+
generationAssetId,
|
|
562
|
+
generationsInBatch,
|
|
563
|
+
generationAspectRatio,
|
|
564
|
+
aspectRatioFromOptions,
|
|
565
|
+
defaultVaultPathFor,
|
|
566
|
+
normalizeVaultPath,
|
|
567
|
+
mergeGenerationPages,
|
|
568
|
+
generationSpecSegments,
|
|
428
569
|
FALLBACK_VIDEO_MODEL_OPTIONS,
|
|
429
570
|
GPT_IMAGE_2_CUSTOM_SIZE,
|
|
430
571
|
validateCustomImageSize,
|
|
@@ -436,6 +577,11 @@ export {
|
|
|
436
577
|
curateComposerModels,
|
|
437
578
|
optionDefault,
|
|
438
579
|
optionChoices,
|
|
439
|
-
reconcileOptionValues
|
|
580
|
+
reconcileOptionValues,
|
|
581
|
+
MEDIA_TYPE_FILTERS,
|
|
582
|
+
GRID_WAVEFORM_BARS,
|
|
583
|
+
WIDE_WAVEFORM_BARS,
|
|
584
|
+
hashSeed,
|
|
585
|
+
previewWaveformBars
|
|
440
586
|
};
|
|
441
|
-
//# sourceMappingURL=chunk-
|
|
587
|
+
//# sourceMappingURL=chunk-LEGZX5MQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/studio/generation.ts","../src/studio/model-options.ts","../src/studio/ports.ts","../src/studio/audio-preview.ts"],"sourcesContent":["import type { ModelOptionsMetadata } from './model-options'\n\n/** Define generation categories for media including image, video, speech, avatar, and transcription */\nexport type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription'\n\n/** Define possible states representing the progress of a generation process */\nexport type GenerationStatus = 'pending' | 'running' | 'succeeded' | 'failed'\n\n/** Define possible status values for a media model's availability and accessibility */\nexport type MediaModelStatus = 'available' | 'limited' | 'unavailable'\n\n/** Define the structure for a generation entity including its metadata and creation details */\nexport interface Generation {\n id: string\n type: string\n prompt: string\n result: string | null\n model: string | null\n cost: number | null\n createdAt: Date | null\n metadata: Record<string, unknown> | null\n}\n\n/** Describe a catalog media model and its optional wire-level option metadata. */\nexport interface MediaModelOption {\n id: string\n name: string\n provider?: string\n type: GenerationType\n status: MediaModelStatus\n reason?: string\n options?: ModelOptionsMetadata\n}\n\n/** Represent media model catalog with default values, model options, and optional error message */\nexport interface MediaModelCatalogResponse {\n defaults: Record<GenerationType, string>\n models: Record<GenerationType, MediaModelOption[]>\n error?: string\n}\n\n// Order drives the library type filter tabs. The composer offers its own\n// subset (`COMPOSER_TYPES` in studio-react) while avatar/transcription are\n// disabled (#451).\n/** Provide an array of supported generation types for media and content processing */\nexport const GENERATION_TYPES: readonly GenerationType[] = ['image', 'video', 'avatar', 'speech', 'transcription']\n\n/** Resolve whether a string value matches a valid GenerationType */\nexport function isGenerationType(value: string): value is GenerationType {\n return (GENERATION_TYPES as readonly string[]).includes(value)\n}\n\n/** Define the minimum number of images required for processing or validation */\nexport const MIN_IMAGE_COUNT = 1\n/** Define the maximum number of images allowed for upload or display */\nexport const MAX_IMAGE_COUNT = 8\n\n/** Resolve a human-readable relative time string from a given date or return an empty string if null */\nexport function relativeTime(date: Date | null): string {\n if (!date) return ''\n const now = Date.now()\n const diff = now - new Date(date).getTime()\n const minutes = Math.floor(diff / 60000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })\n}\n\n/** Resolve the output directory path based on the specified generation type */\nexport function outputPathFor(type: GenerationType): string {\n if (type === 'image') return 'generated/images'\n if (type === 'video') return 'generated/videos'\n if (type === 'avatar') return 'generated/avatars'\n if (type === 'speech') return 'generated/audio'\n return 'generated/transcripts'\n}\n\n/** Resolve the vault path string from a Generation object or return null if unavailable */\nexport function generationVaultPath(generation: Generation): string | null {\n const value = generation.metadata?.vaultPath\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\n/** Resolve selected models by applying defaults for missing or unavailable entries in the catalog */\nexport function selectedModelsWithDefaults(\n current: Partial<Record<GenerationType, string>>,\n catalog: MediaModelCatalogResponse,\n): Partial<Record<GenerationType, string>> {\n const next = { ...current }\n for (const key of GENERATION_TYPES) {\n const models = catalog.models[key] ?? []\n const currentOption = models.find((model) => model.id === next[key])\n // Reset when: no selection, selection not in catalog, or selection is unavailable.\n // This ensures the Generate button is never stuck disabled when routeable\n // models exist but the stored default isn't one of them.\n if (!next[key] || !currentOption || currentOption.status === 'unavailable') {\n next[key] = preferredModelId(key, catalog) ?? ''\n }\n }\n return next\n}\n\n/** Resolve the preferred model ID for a given generation type from the media model catalog */\nexport function preferredModelId(type: GenerationType, catalog: MediaModelCatalogResponse | null): string | undefined {\n if (!catalog) return undefined\n const models = catalog.models[type] ?? []\n const preferred = catalog.defaults[type]\n return models.find((model) => model.id === preferred)?.id\n ?? models.find((model) => model.status !== 'unavailable')?.id\n ?? models[0]?.id\n}\n\n/** Resolve the appropriate status message for a media model based on loading state and availability */\nexport function modelMessage(model: MediaModelOption | undefined, loading: boolean, count: number): string | null {\n if (loading) return 'Loading media models...'\n if (count === 0) return 'No models are available for this media type.'\n if (!model) return 'Select a model.'\n if (model.status === 'unavailable') return model.reason ?? 'This model is not configured.'\n if (model.status === 'limited') return model.reason ? `Limited: ${model.reason}` : 'Limited availability.'\n return null\n}\n\n/** Define fields required to configure and request various types of media generation */\nexport interface GenerationRequestFields {\n workspaceId: string\n clientRequestId: string\n type: GenerationType\n model: string\n prompt: string\n // Every per-lane parameter except the image COUNT is optional, because the\n // composer only sends what the selected model publishes: a model whose\n // metadata omits `size` (or marks it `supported: false`) must send no `size`\n // at all, and a model that publishes nothing — `ltx-video` — sends only the\n // prompt. `count` stays required: it is the number of optimistic cards the\n // caller already drew, not a model parameter.\n image: { size?: string; quality?: string; count: number }\n video: {\n duration?: string | number\n resolution?: string\n aspectRatio?: string\n referenceImageUrl?: string\n audio?: boolean\n mode?: string\n }\n speech: { voice?: string; speed?: number }\n // Optional while the composer lanes are disabled (#451); the server capability stays.\n avatar?: { audioUrl: string; imageUrl: string; avatarId: string }\n transcription?: { audioUrl: string; language: string; responseFormat: string; temperature: string }\n}\n\n// image.count must already be normalized — it is also the optimistic-card count on the caller side\n/** Build the request body object for a generation operation from provided fields */\nexport function buildGenerationRequestBody(fields: GenerationRequestFields): Record<string, unknown> {\n const body: Record<string, unknown> = {\n workspaceId: fields.workspaceId,\n clientRequestId: fields.clientRequestId,\n type: fields.type,\n model: fields.model,\n prompt: fields.prompt.trim(),\n }\n if (fields.type === 'image') {\n if (fields.image.size) body.size = fields.image.size\n if (fields.image.quality) body.quality = fields.image.quality\n body.n = fields.image.count\n }\n if (fields.type === 'video') {\n if (fields.video.duration !== undefined) body.duration = fields.video.duration\n if (fields.video.resolution) body.resolution = fields.video.resolution\n if (fields.video.aspectRatio) body.aspectRatio = fields.video.aspectRatio\n if (fields.video.referenceImageUrl) body.referenceImageUrl = fields.video.referenceImageUrl\n if (fields.video.audio !== undefined) body.audio = fields.video.audio\n if (fields.video.mode) body.mode = fields.video.mode\n }\n if (fields.type === 'speech') {\n if (fields.speech.voice) body.voice = fields.speech.voice\n if (fields.speech.speed !== undefined) body.speed = fields.speech.speed\n }\n if (fields.type === 'avatar' && fields.avatar) Object.assign(body, {\n audioUrl: fields.avatar.audioUrl.trim(),\n imageUrl: fields.avatar.imageUrl.trim() || undefined,\n avatarId: fields.avatar.avatarId.trim() || undefined,\n })\n if (fields.type === 'transcription' && fields.transcription) {\n const temperature = Number(fields.transcription.temperature)\n Object.assign(body, {\n audioUrl: fields.transcription.audioUrl.trim(),\n language: fields.transcription.language.trim() || undefined,\n responseFormat: fields.transcription.responseFormat,\n // omit (let the API default) rather than serialize NaN → null on bad input\n temperature: Number.isFinite(temperature) ? temperature : undefined,\n })\n }\n return body\n}\n\n/** Resolve the current status of a generation based on its metadata and result fields */\nexport function generationStatus(generation: Generation): GenerationStatus {\n const metadata = generation.metadata ?? {}\n const status = typeof metadata.generationStatus === 'string' ? metadata.generationStatus : ''\n if (status === 'pending' || status === 'running' || status === 'failed' || status === 'succeeded') return status\n return generation.result ? 'succeeded' : 'pending'\n}\n\n/** Resolve and return the first user-safe error message from generation metadata or null if none exist */\nexport function generationError(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n if (typeof metadata.providerError === 'string' && metadata.providerError.trim()) {\n return userSafeGenerationMessage(metadata.providerError)\n }\n if (typeof metadata.storageError === 'string' && metadata.storageError.trim()) {\n return metadata.storageError\n }\n return null\n}\n\nfunction generationClientRequestId(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n return typeof metadata.clientRequestId === 'string' && metadata.clientRequestId.trim()\n ? metadata.clientRequestId\n : null\n}\n\nfunction generationBatchSlotKey(generation: Generation): string | null {\n const metadata = generation.metadata ?? {}\n const batchId = typeof metadata.batchId === 'string' && metadata.batchId.trim() ? metadata.batchId : null\n return batchId && typeof metadata.outputIndex === 'number'\n ? `${batchId}:${metadata.outputIndex}`\n : null\n}\n\n/** Resolve a unique merge key from a generation using batch slot or client request ID */\nexport function generationMergeKey(generation: Generation): string | null {\n return generationBatchSlotKey(generation) ?? generationClientRequestId(generation)\n}\n\n/** Merge a new generation into the current list by replacing or prepending it based on matching keys */\nexport function mergeLiveGeneration(current: Generation[], generation: Generation): Generation[] {\n const mergeKey = generationMergeKey(generation)\n const existingIndex = current.findIndex((item) => (\n item.id === generation.id\n || (mergeKey && generationMergeKey(item) === mergeKey)\n ))\n if (existingIndex === -1) return [generation, ...current]\n\n const next = [...current]\n next[existingIndex] = generation\n return next\n}\n\n// Overlay in-flight `live` generations on the loader's rows: each live row leads\n// (prefer the matching loader row by merge key / id so it carries the freshest\n// server state), then the remaining loader rows that no live row already\n// represents — deduped by BOTH id and merge key so a server row and its\n// optimistic twin never both appear. Returns `loader` unchanged when nothing is\n// live. Drives the canvas, library, and polling off one list.\n/** Merge two Generation arrays prioritizing live entries and matching by merge keys or IDs */\nexport function mergeLoaderAndLive(loader: Generation[], live: Generation[]): Generation[] {\n if (live.length === 0) return loader\n const leading = live.map((generation) => {\n const mergeKey = generationMergeKey(generation)\n return mergeKey\n ? loader.find((gen) => generationMergeKey(gen) === mergeKey) ?? generation\n : loader.find((gen) => gen.id === generation.id) ?? generation\n })\n const leadingIds = new Set(leading.map((gen) => gen.id))\n const leadingMergeKeys = new Set(leading\n .map((gen) => generationMergeKey(gen))\n .filter((id): id is string => Boolean(id)))\n return [\n ...leading,\n ...loader.filter((gen) => (\n !leadingIds.has(gen.id)\n && !leadingMergeKeys.has(generationMergeKey(gen) ?? '')\n )),\n ]\n}\n\n/** Determine if a generation ID indicates a local generation */\nexport function isLocalGeneration(generation: Generation): boolean {\n return generation.id.startsWith('local-')\n}\n\nfunction generationOutputIndex(generation: Generation): number {\n const value = generation.metadata?.outputIndex\n return typeof value === 'number' ? value : 0\n}\n\n// The most-recent run: all generations sharing the leading item's clientRequestId\n// (a multi-image batch), ordered by output slot. Falls back to the single leading\n// item when no request id is present. Drives the result canvas.\n/** Resolve and return the latest batch of generations grouped and sorted by client request ID and output index */\nexport function latestBatchOf(generations: Generation[]): Generation[] {\n const first = generations[0]\n if (!first) return []\n const key = generationClientRequestId(first)\n const batch = key\n ? generations.filter((generation) => generationClientRequestId(generation) === key)\n : [first]\n return [...batch].sort((a, b) => generationOutputIndex(a) - generationOutputIndex(b))\n}\n\n/** Resolve a user-safe generation message by filtering sensitive or error-related content */\nexport function userSafeGenerationMessage(message?: string): string {\n if (!message) return 'Generation failed'\n if (/Tangle API key is invalid or expired/i.test(message)) return message\n if (/(api[_ -]?key|secret|token|credential|env|configured|configuration)/i.test(message)) {\n return 'Generation failed'\n }\n return message\n}\n\n/** Generate content optimistically based on input parameters and optional model and output details */\nexport function optimisticGeneration({\n type,\n prompt,\n model,\n clientRequestId,\n outputIndex,\n outputCount,\n}: {\n type: GenerationType\n prompt: string\n model?: string\n clientRequestId: string\n outputIndex?: number\n outputCount?: number\n}, aspectRatio?: number): Generation {\n const batchId = outputIndex == null ? undefined : clientRequestId\n const aspectRatioMetadata = Number.isFinite(aspectRatio) && (aspectRatio ?? 0) > 0\n ? { aspectRatio }\n : {}\n return {\n id: outputIndex == null ? `local-${clientRequestId}` : `local-${clientRequestId}-${outputIndex}`,\n type,\n prompt,\n result: null,\n model: model ?? null,\n cost: null,\n createdAt: new Date(),\n metadata: {\n generationStatus: 'pending',\n provider: type,\n clientRequestId,\n batchId,\n outputIndex,\n outputCount,\n ...aspectRatioMetadata,\n },\n }\n}\n\n/** Mark a generation as failed with updated status and error information */\nexport function failedOptimisticGeneration(generation: Generation): Generation {\n return {\n ...generation,\n metadata: {\n ...(generation.metadata ?? {}),\n generationStatus: 'failed',\n providerError: 'Generation failed',\n },\n }\n}\n\n/** Normalize a value to a finite integer within the allowed image count range */\nexport function normalizeImageCount(value: unknown): number {\n const numeric = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(numeric)) return MIN_IMAGE_COUNT\n return Math.min(Math.max(Math.trunc(numeric), MIN_IMAGE_COUNT), MAX_IMAGE_COUNT)\n}\n\n/** Resolve a generation's batch identity, preferring the server batch id. */\nexport function generationBatchKey(generation: Generation): string {\n const metadata = generation.metadata ?? {}\n if (typeof metadata.batchId === 'string' && metadata.batchId.trim()) return metadata.batchId\n if (typeof metadata.clientRequestId === 'string' && metadata.clientRequestId.trim()) return metadata.clientRequestId\n return generation.id\n}\n\n/** Resolve the stored media asset id, when present. */\nexport function generationAssetId(generation: Generation): string | null {\n const value = generation.metadata?.assetId\n return typeof value === 'string' && value.trim() ? value : null\n}\n\n/** Select and order all outputs belonging to a generation batch. */\nexport function generationsInBatch(generations: readonly Generation[], batchKey: string): Generation[] {\n return generations\n .map((generation, inputIndex) => ({ generation, inputIndex }))\n .filter(({ generation }) => generationBatchKey(generation) === batchKey)\n .sort((left, right) => {\n const leftIndex = left.generation.metadata?.outputIndex\n const rightIndex = right.generation.metadata?.outputIndex\n const leftOrder = typeof leftIndex === 'number' && Number.isFinite(leftIndex) ? leftIndex : Infinity\n const rightOrder = typeof rightIndex === 'number' && Number.isFinite(rightIndex) ? rightIndex : Infinity\n return leftOrder - rightOrder || left.inputIndex - right.inputIndex\n })\n .map(({ generation }) => generation)\n}\n\nfunction ratioFromDimensions(value: string, separator: 'size' | 'aspect'): number | undefined {\n const match = separator === 'size'\n ? /^(\\d+)[x×](\\d+)$/.exec(value)\n : /^(\\d+):(\\d+)$/.exec(value)\n if (!match) return undefined\n const width = Number(match[1])\n const height = Number(match[2])\n return width > 0 && height > 0 ? width / height : undefined\n}\n\nfunction roundedRatio(value: number): number {\n return +value.toFixed(4)\n}\n\n/** Resolve the best available aspect ratio for a generation row. */\nexport function generationAspectRatio(generation: Generation): number {\n const metadata = generation.metadata ?? {}\n if (typeof metadata.aspectRatio === 'number'\n && Number.isFinite(metadata.aspectRatio)\n && metadata.aspectRatio > 0) {\n return roundedRatio(metadata.aspectRatio)\n }\n if (typeof metadata.size === 'string') {\n const ratio = ratioFromDimensions(metadata.size, 'size')\n if (ratio !== undefined) return roundedRatio(ratio)\n }\n if (typeof metadata.aspectRatio === 'string') {\n const ratio = ratioFromDimensions(metadata.aspectRatio, 'aspect')\n if (ratio !== undefined) return roundedRatio(ratio)\n }\n if (generation.type === 'video') return roundedRatio(16 / 9)\n if (generation.type === 'speech' || generation.type === 'audio') return 3.2\n return 1\n}\n\n/** Resolve a requested lane's aspect ratio from its selected options. */\nexport function aspectRatioFromOptions(\n type: GenerationType,\n options: { size?: string; aspectRatio?: string },\n): number | undefined {\n if (type === 'speech') return 3.2\n const ratio = type === 'image'\n ? options.size ? ratioFromDimensions(options.size, 'size') : undefined\n : type === 'video'\n ? options.aspectRatio ? ratioFromDimensions(options.aspectRatio, 'aspect') : undefined\n : undefined\n return ratio === undefined ? undefined : roundedRatio(ratio)\n}\n\n/** Choose the default vault folder shared by a homogeneous media selection. */\nexport function defaultVaultPathFor(generations: readonly Generation[]): string {\n const types = new Set(generations.map((generation) => generation.type))\n if (types.size !== 1) return 'generated/media'\n const [type] = types\n return type !== undefined && isGenerationType(type) ? outputPathFor(type) : 'generated/media'\n}\n\n/** Normalize a user-entered relative vault folder or reject an unsafe path. */\nexport function normalizeVaultPath(input: string): string | null {\n const path = input.trim().replace(/^\\/+|\\/+$/g, '').replace(/\\/{2,}/g, '/')\n if (!path) return null\n const segments = path.split('/')\n return segments.some((segment) => segment === '.' || segment === '..' || segment.includes('\\\\'))\n ? null\n : path\n}\n\n/** Append a page while preserving the first row seen for each id. */\nexport function mergeGenerationPages(prev: readonly Generation[], next: readonly Generation[]): Generation[] {\n const seen = new Set(prev.map((generation) => generation.id))\n const merged = [...prev]\n for (const generation of next) {\n if (seen.has(generation.id)) continue\n seen.add(generation.id)\n merged.push(generation)\n }\n return merged\n}\n\n/** Resolve only the human-readable media specification fields a row carries. */\nexport function generationSpecSegments(generation: Generation): string[] {\n const metadata = generation.metadata ?? {}\n const segments: string[] = []\n if (typeof metadata.size === 'string') segments.push(metadata.size.replace(/x/g, '×'))\n if (typeof metadata.resolution === 'string') segments.push(metadata.resolution)\n if (typeof metadata.aspectRatio === 'string' && /^(\\d+):(\\d+)$/.test(metadata.aspectRatio)) {\n segments.push(metadata.aspectRatio)\n }\n if (typeof metadata.duration === 'string') {\n segments.push(metadata.duration)\n } else if (typeof metadata.durationSeconds === 'number' && Number.isFinite(metadata.durationSeconds)) {\n const seconds = Math.max(0, Math.floor(metadata.durationSeconds))\n segments.push(`${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`)\n }\n if (typeof metadata.voice === 'string') segments.push(metadata.voice)\n return segments\n}\n","import type { GenerationType, MediaModelOption } from './generation'\n\n/** A wire-typed value accepted by a model option. */\nexport type ModelOptionValue = string | number | boolean\n\n/** Per-parameter option metadata, structurally identical to tangle-router's\n * `ModelOptionMetadata` (lib/model-options.ts, shipped in router PR #429).\n * `supported: false` means the model lacks or ignores the parameter.\n * `values` is the exact wire-typed enum; `min` and `max` are inclusive.\n * `default` applies when the caller omits the parameter. An absent entry or\n * options object means unknown, so consumers must not invent a value. */\nexport interface ModelOptionMetadata {\n supported?: boolean\n values?: readonly ModelOptionValue[]\n min?: number\n max?: number\n default?: ModelOptionValue\n}\n\n/** Per-parameter model option metadata keyed by the provider's wire field. */\nexport type ModelOptionsMetadata = Readonly<Record<string, ModelOptionMetadata>>\n\nconst SEEDANCE_2_0: ModelOptionsMetadata = {\n duration: {\n values: ['auto', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'],\n default: 'auto',\n },\n resolution: { values: ['480p', '720p', '1080p', '4k'], default: '720p' },\n aspect_ratio: { values: ['auto', '21:9', '16:9', '4:3', '1:1', '3:4', '9:16'], default: 'auto' },\n audio: { default: true },\n}\n\n// These values mirror tangle-router VIDEO_MODEL_OPTIONS from PR #429,\n// observedAt 2026-08-19. Catalog-provided live options always win.\n/** Fallback video options matching tangle-router's wire-exact metadata. */\nexport const FALLBACK_VIDEO_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'runway/gen4.5': {\n duration: { min: 2, max: 10, default: 5 },\n aspect_ratio: { values: ['16:9', '9:16'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n },\n 'runway/gen4_turbo': {\n duration: { min: 2, max: 10, default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '4:3', '3:4', '1:1', '21:9'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n },\n 'kling/kling-v1-6': {\n duration: { values: [5, 10], default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n mode: { values: ['std', 'pro'], default: 'std' },\n },\n 'kling/kling-v2-master': {\n duration: { values: [5, 10], default: 5 },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n resolution: { supported: false },\n audio: { supported: false },\n mode: { supported: false },\n },\n 'bytedance/seedance-2.0/text-to-video': SEEDANCE_2_0,\n 'bytedance/seedance-2.0/image-to-video': SEEDANCE_2_0,\n 'fal-ai/kling-video/v3/pro/text-to-video': {\n duration: { values: ['3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'], default: '5' },\n resolution: { supported: false },\n aspect_ratio: { values: ['16:9', '9:16', '1:1'], default: '16:9' },\n audio: { default: true },\n },\n 'fal-ai/veo3.1': {\n duration: { values: ['4s', '6s', '8s'], default: '8s' },\n resolution: { values: ['720p', '1080p', '4k'], default: '720p' },\n aspect_ratio: { values: ['16:9', '9:16'], default: '16:9' },\n audio: { default: true },\n },\n 'xai/grok-imagine-video/text-to-video': {\n duration: { min: 1, max: 15, default: 6 },\n resolution: { values: ['480p', '720p'], default: '720p' },\n aspect_ratio: { values: ['16:9', '4:3', '3:2', '1:1', '2:3', '3:4', '9:16'], default: '16:9' },\n audio: { supported: false },\n },\n}\n\n// Source: provider research recorded in router #420 and agent-app #449 on\n// 2026-08-18. Live catalog options remain authoritative when present.\nconst IMAGE_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'gpt-image-2': {\n size: { values: ['auto', '1024x1024', '1536x1024', '1024x1536'], default: 'auto' },\n quality: { values: ['low', 'medium', 'high', 'auto'], default: 'auto' },\n n: { values: [1, 2, 4, 8], default: 1 },\n },\n}\n\nconst OPENAI_TTS_VOICES = ['alloy', 'ash', 'coral', 'echo', 'fable', 'onyx', 'nova', 'sage', 'shimmer'] as const\nconst OPENAI_GPT4O_MINI_TTS_VOICES = [...OPENAI_TTS_VOICES, 'ballad', 'cedar', 'marin', 'verse'] as const\n// These aliases are the router's GEMINI_VOICE_MAP keys. The router translates\n// them to Kore, Puck, Charon, Algenib, Aoede, and Leda respectively.\nconst GOOGLE_TTS_VOICE_ALIASES = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'] as const\n\nconst OPENAI_AUDIO_MODEL_OPTIONS: Readonly<Record<string, ModelOptionsMetadata>> = {\n 'tts-1': {\n voice: { values: OPENAI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n 'tts-1-hd': {\n voice: { values: OPENAI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n 'gpt-4o-mini-tts': {\n voice: { values: OPENAI_GPT4O_MINI_TTS_VOICES, default: 'alloy' },\n speed: { min: 0.25, max: 4, default: 1 },\n },\n}\n\nconst GOOGLE_AUDIO_MODEL_OPTIONS: ModelOptionsMetadata = {\n voice: { values: GOOGLE_TTS_VOICE_ALIASES, default: 'alloy' },\n speed: { supported: false },\n}\n\n// Mistral's preset voices are enumerable only through its authenticated\n// /v1/audio/voices API; no publicly verifiable list existed at research time.\n// Unknown means show nothing invented, so Voxtral uses the router's provider\n// default (gb_jane_neutral) until router #420 publishes live catalog options.\n\n/** UI constraints for a custom gpt-image-2 size. */\nexport const GPT_IMAGE_2_CUSTOM_SIZE = { multipleOf: 16, maxLongEdge: 3840, maxRatio: 3 } as const\n\n/** Validate a custom gpt-image-2 size against its published UI constraints. */\nexport function validateCustomImageSize(width: number, height: number): { ok: true } | { ok: false; reason: string } {\n if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {\n return { ok: false, reason: 'Width and height must be positive integers.' }\n }\n if (width % GPT_IMAGE_2_CUSTOM_SIZE.multipleOf !== 0 || height % GPT_IMAGE_2_CUSTOM_SIZE.multipleOf !== 0) {\n return { ok: false, reason: 'Each side must be a multiple of 16.' }\n }\n if (Math.max(width, height) > GPT_IMAGE_2_CUSTOM_SIZE.maxLongEdge) {\n return { ok: false, reason: 'The long edge must be 3840 pixels or less.' }\n }\n if (Math.max(width / height, height / width) > GPT_IMAGE_2_CUSTOM_SIZE.maxRatio) {\n return { ok: false, reason: 'The aspect ratio must be between 1:3 and 3:1.' }\n }\n return { ok: true }\n}\n\nconst KNOWN_PROVIDER_ALIASES = new Set([\n 'openai',\n 'google',\n 'gemini',\n 'fal',\n 'fal-ai',\n 'runway',\n 'kling',\n 'bytedance',\n 'xai',\n])\n\nfunction bareSingleSlashId(modelId: string): string | undefined {\n const segments = modelId.split('/')\n if (segments.length !== 2 || !KNOWN_PROVIDER_ALIASES.has(segments[0] ?? '')) return undefined\n return segments[1]\n}\n\nfunction audioOptions(modelId: string, provider?: string): ModelOptionsMetadata | undefined {\n const bareId = bareSingleSlashId(modelId) ?? modelId\n const exact = OPENAI_AUDIO_MODEL_OPTIONS[modelId] ?? OPENAI_AUDIO_MODEL_OPTIONS[bareId]\n if (exact) return exact\n\n const normalizedProvider = provider?.toLowerCase()\n if (\n (bareId.toLowerCase().startsWith('gemini') && bareId.toLowerCase().includes('tts'))\n || normalizedProvider === 'google'\n || normalizedProvider === 'gemini'\n ) return GOOGLE_AUDIO_MODEL_OPTIONS\n\n return undefined\n}\n\n/** Resolve live catalog options first, then exact or safe single-prefix fallbacks. */\nexport function resolveComposerOptions(input: {\n type: 'image' | 'video' | 'speech'\n modelId: string\n provider?: string\n catalogOptions?: ModelOptionsMetadata\n}): ModelOptionsMetadata | undefined {\n if (input.catalogOptions) return input.catalogOptions\n if (input.type === 'speech') return audioOptions(input.modelId, input.provider)\n\n const table = input.type === 'image' ? IMAGE_MODEL_OPTIONS : FALLBACK_VIDEO_MODEL_OPTIONS\n const exact = table[input.modelId]\n if (exact) return exact\n\n // Only a known provider prefix on an id with exactly one slash is stripped;\n // multi-slash fal ids must remain whole.\n const bareId = bareSingleSlashId(input.modelId)\n return bareId ? table[bareId] : undefined\n}\n\n/** Return whether a model supports the gpt-image-2 custom-size rule. */\nexport function supportsCustomImageSize(modelId: string): boolean {\n return modelId === 'gpt-image-2' || bareSingleSlashId(modelId) === 'gpt-image-2'\n}\n\n/** Map verified text-to-video model ids to their image-to-video siblings. */\nexport const IMAGE_TO_VIDEO_SIBLINGS: Readonly<Record<string, string>> = {\n 'bytedance/seedance-2.0/text-to-video': 'bytedance/seedance-2.0/image-to-video',\n}\n\n/** Resolve a verified image-to-video sibling for a text-to-video model. */\nexport function imageToVideoSibling(modelId: string): string | undefined {\n return IMAGE_TO_VIDEO_SIBLINGS[modelId]\n}\n\n/** Resolve the verified text-to-video sibling for an image-to-video model. */\nexport function textToVideoSibling(modelId: string): string | undefined {\n return Object.entries(IMAGE_TO_VIDEO_SIBLINGS).find(([, sibling]) => sibling === modelId)?.[0]\n}\n\n/** Curate catalog models for the issue #449 composer lanes. */\nexport function curateComposerModels(\n type: GenerationType,\n models: MediaModelOption[],\n): MediaModelOption[] {\n if (type === 'image') return models.filter((model) => supportsCustomImageSize(model.id))\n if (type === 'video') {\n const imageToVideoIds = new Set(Object.values(IMAGE_TO_VIDEO_SIBLINGS))\n return models.filter((model) => !model.id.toLowerCase().includes('sora') && !imageToVideoIds.has(model.id))\n }\n return models\n}\n\n/** Resolve an option default from its default, values, or lower bound. */\nexport function optionDefault(meta: ModelOptionMetadata): ModelOptionValue | undefined {\n return meta.default ?? meta.values?.[0] ?? meta.min\n}\n\n/** Return exact enum choices or an inclusive integer range. */\nexport function optionChoices(meta: ModelOptionMetadata): readonly ModelOptionValue[] {\n if (meta.values) return meta.values\n if (meta.min == null || meta.max == null) return []\n const values: number[] = []\n for (let value = Math.ceil(meta.min); value <= Math.floor(meta.max); value += 1) values.push(value)\n return values\n}\n\nfunction isCustomSize(value: ModelOptionValue): boolean {\n if (typeof value !== 'string') return false\n const match = /^(\\d+)x(\\d+)$/.exec(value)\n if (!match) return false\n return validateCustomImageSize(Number(match[1]), Number(match[2])).ok\n}\n\nfunction isLegalOptionValue(meta: ModelOptionMetadata, value: ModelOptionValue): boolean {\n if (meta.values) return meta.values.includes(value)\n if (typeof value === 'number' && meta.min != null && meta.max != null) {\n return value >= meta.min && value <= meta.max\n }\n return meta.min == null && meta.max == null\n}\n\n/** Reconcile selections against supported options and their wire-typed defaults.\n * `allowCustomSize` keeps a legal off-enum `WxH` size selection (gpt-image-2's\n * custom-size rule) — the caller decides via {@link supportsCustomImageSize},\n * so the check holds even when the options came from the live catalog. */\nexport function reconcileOptionValues(\n options: ModelOptionsMetadata | undefined,\n current: Readonly<Record<string, ModelOptionValue>>,\n opts?: { allowCustomSize?: boolean },\n): Record<string, ModelOptionValue> {\n if (!options) return {}\n const reconciled: Record<string, ModelOptionValue> = {}\n for (const [key, meta] of Object.entries(options)) {\n if (meta.supported === false) continue\n const selected = current[key]\n const customSizeIsLegal = key === 'size'\n && selected !== undefined\n && opts?.allowCustomSize === true\n && isCustomSize(selected)\n const selectionIsLegal = selected !== undefined\n && (isLegalOptionValue(meta, selected) || customSizeIsLegal)\n const next = selectionIsLegal ? selected : optionDefault(meta)\n if (next !== undefined) reconciled[key] = next\n }\n return reconciled\n}\n","/**\n * Product seams for the studio media library. A product such as gtm-agent\n * implements these against its `/api/generations`, `/api/media/save`, and\n * `/api/generations/bulk-delete` routes. The ports themselves are never fetched\n * by the shell; note the assembled screens ALSO require the host to serve\n * `StudioComposer`'s `/api/generate` and `/api/media-models` (see\n * `../studio-react/index.tsx`), and `useStudioGenerations` polls its\n * `generationsEndpoint` (default `/api/generations`).\n */\n\nimport type { Generation } from './generation'\n\nexport type MediaTypeFilter = 'all' | 'image' | 'video' | 'speech'\n\nexport const MEDIA_TYPE_FILTERS: readonly { value: MediaTypeFilter; label: string }[] = [\n { value: 'all', label: 'All media' },\n { value: 'image', label: 'Images' },\n { value: 'video', label: 'Videos' },\n { value: 'speech', label: 'Audio' },\n]\n\nexport interface GenerationPageQuery {\n /** Trimmed prompt search; '' means no filter. Goes on the wire as `q`. */\n q: string\n type: MediaTypeFilter\n /** null for the first page; otherwise the server's opaque cursor. */\n cursor: string | null\n signal: AbortSignal\n}\n\nexport interface GenerationPage {\n items: Generation[]\n nextCursor?: string\n}\n\nexport type FetchGenerationsPage = (query: GenerationPageQuery) => Promise<GenerationPage>\n\nexport interface VaultSaveResult {\n generationId: string\n vaultPath: string\n}\n\nexport type SaveGenerationsToVault = (input: {\n generations: readonly Generation[]\n path: string\n signal?: AbortSignal\n}) => Promise<readonly VaultSaveResult[]>\n\n/** The shell may invoke this during page teardown (the `pagehide`/unmount flush\n * of a deferred delete). Implementations must use unload-survivable transport\n * such as `fetch(..., { keepalive: true })` or `navigator.sendBeacon`, and keep\n * the payload within the keepalive transport's approximately 64 KB bound. */\nexport type DeleteGenerations = (ids: readonly string[]) => Promise<void>\nexport type DownloadGenerations = (generations: readonly Generation[]) => void | Promise<void>\n\n/** Every media action a tile / viewer / batch bar can offer. An ABSENT member\n * hides its control — a product with no vault endpoint must not render a\n * \"Save to vault\" button that does nothing. */\nexport interface StudioMediaActions {\n download?: DownloadGenerations\n save?: SaveGenerationsToVault\n remove?: DeleteGenerations\n vaultHref?: (filePath?: string | null) => string\n /** Intercept the vault link for SPA nav; the href stays for middle-click. */\n onOpenVault?: (generation: Generation) => void\n}\n","/** A deterministic waveform bar used when decoded audio is unavailable. */\nexport interface WaveformBar {\n /** 7–100, % of tile height. */\n heightPct: number\n opacity: number\n}\n\nexport const GRID_WAVEFORM_BARS = 26\nexport const WIDE_WAVEFORM_BARS = 72\n\n/** Hash a string with FNV-1a into an unsigned 32-bit seed. */\nexport function hashSeed(value: string): number {\n let hash = 0x811C9DC5\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index)\n hash = Math.imul(hash, 0x01000193)\n }\n return hash >>> 0\n}\n\nfunction mulberry32(seed: number): () => number {\n let a = seed\n return () => {\n a |= 0\n a = a + 0x6D2B79F5 | 0\n let t = Math.imul(a ^ a >>> 15, 1 | a)\n t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t\n return ((t ^ t >>> 14) >>> 0) / 4294967296\n }\n}\n\n/** Build stable pseudo-waveform bars for a media preview. */\nexport function previewWaveformBars(seed: string, count: number): readonly WaveformBar[] {\n const rnd = mulberry32(hashSeed(seed))\n return Array.from({ length: count }, (_, index) => {\n const t = count > 1 ? index / (count - 1) : 0\n const env = Math.sin(Math.PI * t) * 0.55 + 0.45\n const heightPct = +Math.max(7, (0.22 + rnd() * 0.78) * env * 92).toFixed(1)\n const opacity = +(0.5 + rnd() * 0.5).toFixed(2)\n return { heightPct, opacity }\n })\n}\n"],"mappings":"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","names":[]}
|