@tangle-network/agent-app 0.46.4 → 0.46.6
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/dist/assistant/index.js +6 -4
- package/dist/assistant/index.js.map +1 -1
- package/dist/chat-react/index.d.ts +10 -24
- package/dist/chat-react/index.js +6 -127
- package/dist/chat-react/index.js.map +1 -1
- package/dist/{chunk-RLOHIX5Y.js → chunk-3HUFJ5LO.js} +1 -13
- package/dist/chunk-3HUFJ5LO.js.map +1 -0
- package/dist/chunk-5SSUCLBW.js +1896 -0
- package/dist/chunk-5SSUCLBW.js.map +1 -0
- package/dist/{chunk-YTEUZXX2.js → chunk-JAYKCWW4.js} +619 -1671
- package/dist/chunk-JAYKCWW4.js.map +1 -0
- package/dist/chunk-L7MB2LLM.js +15 -0
- package/dist/chunk-L7MB2LLM.js.map +1 -0
- package/dist/{chunk-LEGZX5MQ.js → chunk-OOE2TPY2.js} +6 -2
- package/dist/chunk-OOE2TPY2.js.map +1 -0
- package/dist/{mention-editor-GVLG3W7C.js → mention-editor-WW7UVZMR.js} +5 -3
- package/dist/{mention-editor-GVLG3W7C.js.map → mention-editor-WW7UVZMR.js.map} +1 -1
- package/dist/studio/generation.d.ts +10 -2
- package/dist/studio/index.js +3 -1
- package/dist/studio-react/composer-option-controls.d.ts +3 -1
- package/dist/studio-react/index.js +40 -22
- package/dist/studio-react/index.js.map +1 -1
- package/dist/{chat-react → web-react}/composer-mode-controls.d.ts +11 -1
- package/dist/{chat-react → web-react}/entry-composer.d.ts +11 -8
- package/dist/web-react/index.d.ts +2 -0
- package/dist/web-react/index.js +14 -9
- package/package.json +1 -1
- package/dist/chat-react/types.d.ts +0 -11
- package/dist/chunk-DINGA2MO.js +0 -718
- package/dist/chunk-DINGA2MO.js.map +0 -1
- package/dist/chunk-LEGZX5MQ.js.map +0 -1
- package/dist/chunk-RLOHIX5Y.js.map +0 -1
- package/dist/chunk-YTEUZXX2.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-react/composer-file-accept.ts","../src/web-react/use-composer-attachments.ts","../src/web-react/harness-glyphs.tsx","../src/web-react/agent-session-controls.tsx"],"sourcesContent":["/**\n * The composer's file-ingress filter, and the clipboard rename that goes with\n * it.\n *\n * A file reaches a composer by three routes — the picker dialog, a drag-and-drop,\n * and a clipboard paste — and only the picker gets a native `accept` filter (one\n * the user can defeat with \"All Files\"). Every route therefore funnels through\n * {@link filterAcceptedFiles}, so a type the picker will not offer cannot arrive\n * by another route instead.\n *\n * One route can still reach a different verdict, and it does so deliberately.\n * Paste is the only route that RENAMES, and {@link renamePastedImages} names a\n * file after the type it declares. So a clipboard bitmap called `image.png` that\n * declares `image/heic` is judged as `.heic` on paste, while the picker and a\n * drop judge the name they were handed and admit it under `accept=\".png\"`. The\n * filter is the same on all three; what differs is the name it is given, and\n * paste is stricter precisely because a rename that kept the contradicting name\n * would let the composer manufacture its own way past the filter.\n *\n * ONE accept matcher serves the package: `ChatComposer` gates its ingress on it\n * and `useComposerAttachments` gates `addFiles` on it. A second implementation\n * of the `accept` grammar is how the two ends of the same staging path start\n * disagreeing about what a file is.\n *\n * Pure data in, pure data out — nothing here throws, logs, or touches the DOM,\n * so a caller decides how a rejection is surfaced. Import-free beyond the\n * browser's own `File`, which keeps it usable from `/web-react`'s client bundle.\n */\n\n/** A file the `accept` list refused, with the reason to show for it. */\nexport interface ComposerFileRejection {\n file: File\n reason: string\n}\n\n/**\n * Checks one file against a comma-separated `accept` list, using the grammar of\n * the native `<input accept>` attribute: extensions (`.png`), exact MIME types\n * (`image/png`), and MIME wildcards (`image/*`). An absent or empty list accepts\n * everything, which is what an unset `accept` prop means.\n */\nexport function isAcceptedFileType(file: File, accept?: string): boolean {\n if (!accept || accept.trim().length === 0) return true\n\n const patterns = accept\n .split(',')\n .map((pattern) => pattern.trim())\n .filter((pattern) => pattern.length > 0)\n if (patterns.length === 0) return true\n\n const name = file.name.toLowerCase()\n const type = (file.type || '').toLowerCase()\n\n return patterns.some((pattern) => {\n const lower = pattern.toLowerCase()\n if (lower.startsWith('.')) return name.endsWith(lower)\n if (lower.endsWith('/*')) {\n // Keep the trailing \"/\" so `image/*` cannot match `imagex/png`, and\n // require a real subtype after it: `image/` and `image//png` are\n // malformed types, and a `File.type` can carry either.\n const prefix = lower.slice(0, -1)\n if (!type.startsWith(prefix)) return false\n const subtype = type.slice(prefix.length)\n return subtype.length > 0 && !subtype.includes('/')\n }\n return type === lower\n })\n}\n\n/** The reason an `accept` list refused a file. One wording for every ingress\n * route, so the same file reads the same whether it was picked or dropped. */\nexport function acceptRejectionReason(file: File, accept: string): string {\n return `\"${file.name}\" is not an accepted file type (${accept}).`\n}\n\n/**\n * Splits a batch into what the `accept` list admits and what it refuses. Size\n * and count limits are NOT applied here: they belong to the staging queue, which\n * knows what is already staged (`useComposerAttachments`), while this runs at the\n * composer's edge where that is unknown.\n */\nexport function filterAcceptedFiles(\n files: File[] | FileList,\n accept?: string,\n): { accepted: File[]; rejected: ComposerFileRejection[] } {\n const list = Array.isArray(files) ? files : Array.from(files)\n const accepted: File[] = []\n const rejected: ComposerFileRejection[] = []\n for (const file of list) {\n if (isAcceptedFileType(file, accept)) accepted.push(file)\n else rejected.push({ file, reason: acceptRejectionReason(file, accept ?? '') })\n }\n return { accepted, rejected }\n}\n\n/**\n * Extensions that truthfully name each image type, most canonical first. A type\n * with more than one is the reason this is a LIST and not a single name: `.jpeg`\n * and `.jpg` are the same claim, so a rename that swapped one for the other\n * would make a paste fail an `accept=\".jpeg\"` list that the very same file\n * passes through the picker.\n *\n * A type absent from here derives its single extension from the MIME subtype.\n */\nconst IMAGE_EXTENSIONS_BY_MIME: Record<string, readonly string[]> = {\n 'image/png': ['png'],\n 'image/jpeg': ['jpg', 'jpeg', 'jpe'],\n 'image/jpg': ['jpg', 'jpeg'],\n 'image/gif': ['gif'],\n 'image/webp': ['webp'],\n 'image/bmp': ['bmp'],\n 'image/svg+xml': ['svg'],\n 'image/tiff': ['tiff', 'tif'],\n 'image/x-icon': ['ico'],\n 'image/vnd.microsoft.icon': ['ico'],\n}\n\n/**\n * Matches EXACTLY the generic names browsers give a clipboard bitmap: an empty\n * name, `image`, or `image.<ext>`. Those are the names that collide across\n * pastes. A name that is only an extension (`.png`) is a real, if unusual,\n * filename and is left alone.\n */\nfunction isGenericImageName(name: string): boolean {\n return /^(?:image(?:\\.[a-z0-9]+)?)?$/i.test(name.trim())\n}\n\n/**\n * The extension a renamed clipboard image should carry, or null when none can\n * be derived truthfully.\n *\n * It must never name a format the bytes are not. A rename that hands an\n * `accept=\".png\"` list a file satisfying it by its new name alone turns the\n * rename into a way around the very gate it is filtered by.\n *\n * So the DECLARED TYPE decides which extensions are truthful, and the filename\n * may only pick among those. A clipboard file can carry a name whose extension\n * contradicts its type — a bitmap named `image.png` that is really `image/heic`\n * — and letting the name win there is exactly the hole this order closes. But\n * when the name's extension is one the type itself allows, it is kept: an\n * `image.jpeg` of type `image/jpeg` stays `.jpeg`, because rewriting it to the\n * canonical `.jpg` would make the paste fail an `accept=\".jpeg\"` list that the\n * same file passes through the picker.\n *\n * A subtype only stands in for an extension when it is already shaped like one.\n * `image/heic` is; `image/vnd.microsoft.icon` is not, and squeezing the\n * punctuation out of it would yield `vndmicrosofticon` — a name no accept list\n * will ever match, so a perfectly good `.ico` would be admitted through the\n * picker and refused on paste. An unrecognised compound type therefore names\n * nothing, and the filename is consulted instead.\n *\n * The filename is consulted on its own ONLY when the type names nothing usable\n * — either no subtype at all (`image/`) or a compound one. That is not an\n * exception to the rule above: the only named files this function ever sees are\n * `image.<ext>` (see {@link isGenericImageName}), so the fallback can only ever\n * preserve an extension the name already carried — it cannot manufacture one,\n * and an accept list therefore decides the same way it would have without the\n * rename. When nothing yields an extension, the caller skips the rename rather\n * than guessing.\n */\nfunction imageExtension(file: File): string | null {\n const type = file.type.toLowerCase()\n const fromName = /\\.([a-z0-9]+)$/i.exec(file.name)?.[1]?.toLowerCase()\n\n const mapped = IMAGE_EXTENSIONS_BY_MIME[type]\n const subtype = type.startsWith('image/') ? type.slice('image/'.length) : ''\n const bare = subtype.split('+')[0] ?? ''\n const truthful = mapped ?? (/^[a-z0-9]+$/.test(bare) ? [bare] : [])\n\n if (truthful.length === 0) return fromName ?? null\n if (fromName !== undefined && truthful.includes(fromName)) return fromName\n return truthful[0] ?? null\n}\n\n/** The `pasted-image-<n>` numbers these names already occupy. */\nfunction takenPastedImageIndexes(names: Iterable<string>): Set<number> {\n const taken = new Set<number>()\n for (const name of names) {\n const digits = /^pasted-image-(\\d+)(?:\\.[a-z0-9]+)?$/i.exec(name.trim())?.[1]\n if (digits === undefined) continue\n const parsed = Number.parseInt(digits, 10)\n if (Number.isSafeInteger(parsed)) taken.add(parsed)\n }\n return taken\n}\n\n/**\n * The lowest number above `after` that no name has claimed.\n *\n * Searching upward from the caller's own count — rather than from the highest\n * number any staged name happens to carry — is what keeps the usual case cheap:\n * staged numbers are avoided, never followed, so the walk advances at most once\n * per number already claimed.\n *\n * Termination does not rest on that, though, because `after` can be pushed near\n * the safe-integer ceiling and `+ 1` stops moving there — a walk that only\n * tested membership would spin on one value forever. The upward walk therefore\n * ends the moment a candidate leaves the safe range, and the search restarts\n * from the bottom, where a free number is guaranteed: `taken` is finite, so one\n * of the first `taken.size + 1` positive integers is always free. Every number\n * this returns is a safe integer, which is also what keeps an unsafe staged\n * name — invisible to {@link takenPastedImageIndexes} — impossible to collide\n * with.\n */\nfunction nextFreeIndex(taken: Set<number>, after: number): number {\n const start = Number.isSafeInteger(after) && after >= 0 ? after + 1 : 1\n for (let candidate = start; Number.isSafeInteger(candidate); candidate += 1) {\n if (!taken.has(candidate)) return candidate\n }\n for (let candidate = 1; ; candidate += 1) {\n if (!taken.has(candidate)) return candidate\n }\n}\n\n/**\n * Gives every generically-named clipboard image a distinct\n * `pasted-image-<n>.<ext>` name. Two pastes of the same bitmap otherwise arrive\n * as `image.png` twice, and a staging queue that keys on the name treats the\n * second as a duplicate of the first.\n *\n * A number is never reused. The search avoids every `pasted-image-<n>` already\n * present in `stagedNames` (the queue the host still holds, which outlives this\n * composer's own count) and in the batch itself (one paste can carry a file\n * already named that way beside a raw bitmap), so a collision is not reachable\n * rather than merely unlikely. `startIndex` is the caller's running count, and\n * `nextIndex` is the count to hand the next paste.\n *\n * Files that already carry a real name pass through untouched, so a copied\n * `report.pdf` keeps being `report.pdf`. So does an image whose extension\n * cannot be derived from what it declares — a renamed file must never claim a\n * format it is not. Only the name changes: the bytes, the type and the\n * modification time travel with it, so downstream fingerprinting still sees\n * the file the user pasted.\n */\nexport function renamePastedImages(\n files: File[],\n startIndex: number,\n stagedNames: Iterable<string> = [],\n): { files: File[]; nextIndex: number } {\n const taken = takenPastedImageIndexes([...stagedNames, ...files.map((file) => file.name)])\n // Whatever the caller's count is — a negative, a fraction, NaN, or a value at\n // the ceiling — `nextFreeIndex` normalises it and always answers with a safe\n // integer, so nothing here has to pre-screen it.\n let nextIndex = startIndex\n const renamed = files.map((file) => {\n const typed = file.type.startsWith('image/')\n // A clipboard file can arrive with no MIME type at all, and one named\n // `image.png` still collides across pastes exactly as a typed one does.\n // Its extension then comes from its own name, which cannot manufacture a\n // claim it did not already carry.\n if (!isGenericImageName(file.name) || (!typed && file.type !== '')) return file\n const extension = imageExtension(file)\n if (extension === null) return file\n nextIndex = nextFreeIndex(taken, nextIndex)\n taken.add(nextIndex)\n return new File([file], `pasted-image-${nextIndex}.${extension}`, {\n type: file.type,\n lastModified: file.lastModified,\n })\n })\n return { files: renamed, nextIndex }\n}\n","/**\n * `useComposerAttachments` — the composer's staged-upload lifecycle: validate\n * selected/dropped/pasted files against the shared limits (the SAME\n * `sniffBinary`/`checkAttachmentType`/size-cap vocabulary the store-backed\n * upload route enforces server-side, `../chat-routes/attachment-validation`\n * + `../chat-routes/binary-sniff`), upload each accepted file with one POST\n * request per file (so a single failure never poisons the batch), and track\n * every file's status so a host composer can render chips and gate sending.\n *\n * Ported from gtm-agent's `src/components/composer-attachments.tsx`\n * (gtm#584/#592/#593 hardened the sniff gate and batch semantics this leans\n * on), de-gtm-ified:\n * - the hardcoded `/api/vault/upload?workspaceId=` URL becomes\n * `uploadUrl`/`buildUploadRequest` (the latter wins — it hands back both\n * the URL and a `RequestInit` override, e.g. an auth header);\n * - `sonner` toasts become `onReject` (client pre-validation, never hits the\n * network) and `onError` (a request that reached the server and failed);\n * - the `accept`-string gate comes from `./composer-file-accept`, the one\n * matcher `ChatComposer` also funnels its picker/drop/paste ingress\n * through, so both ends of the staging path admit the same files;\n * - the response is expected to be `{ files: ChatAttachmentInput[] }` (full\n * server-authoritative descriptors — size/mediaType/kind — not gtm's\n * `{path, name}`), so `references` is a verbatim pass-through with no\n * client recompute;\n * - `workspaceId`'s truthiness gate becomes `enabled` (default `true`).\n *\n * Import-free beyond React + the browser-safe `/chat-routes` validation core:\n * this module ships through `/web-react` into client bundles\n * (`tests/browser-safe-subpaths.test.ts` walks the graph), so nothing here\n * may reach a Node builtin, `sandbox-ui`, or an engine package.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ChatAttachmentInput, ChatAttachmentKind } from './chat-stream'\nimport type { ComposerFile } from './chat-composer'\nimport { acceptRejectionReason, isAcceptedFileType } from './composer-file-accept'\nimport {\n ATTACHMENT_ACCEPT,\n ATTACHMENT_MAX_COUNT,\n MAX_ATTACHMENT_TOTAL_BYTES,\n MAX_BINARY_ATTACHMENT_BYTES,\n MAX_TEXT_ATTACHMENT_BYTES,\n attachmentSizeErrorMessage,\n attachmentTotalSizeErrorMessage,\n checkAttachmentType,\n sanitizeAttachmentFileName,\n} from '../chat-routes/attachment-validation'\nimport { sniffBinary } from '../chat-routes/binary-sniff'\n\nexport { ATTACHMENT_ACCEPT } from '../chat-routes/attachment-validation'\n\n/** One staged file and its upload lifecycle. `file` is retained so a failed\n * upload can be retried without re-selecting; `previewUrl` is an object URL\n * for image thumbnails and must be revoked when the entry leaves the queue.\n * `reference` is the server's authoritative descriptor once the upload\n * lands — stored verbatim, never recomputed client-side. */\ninterface StagedAttachment {\n id: string\n file: File\n name: string\n size: number\n status: 'pending' | 'uploading' | 'ready' | 'error'\n reference?: ChatAttachmentInput\n previewUrl?: string\n errorMessage?: string\n}\n\n/** Define options for configuring file upload behavior and handling in a composer component */\nexport interface UseComposerAttachmentsOptions {\n /** Simple upload target: every file POSTs here. Ignored when\n * `buildUploadRequest` is provided. */\n uploadUrl?: string\n /** Full request-building seam (auth headers, per-file routing, …) — wins\n * over `uploadUrl` when both are set. */\n buildUploadRequest?: (args: { file: File; name: string; form: FormData }) => {\n url: string\n init?: Omit<RequestInit, 'body' | 'signal'>\n }\n /** Client pre-validation rejections — a file that never reaches the\n * network (bad type, over a size cap, over count, disallowed kind). */\n onReject?: (reason: string, file?: File) => void\n /** A file that reached the upload endpoint and failed (HTTP error,\n * transport error, malformed response). */\n onError?: (reason: string) => void\n limits?: {\n maxCount?: number\n maxBinaryBytes?: number\n maxTextBytes?: number\n maxTotalBytes?: number\n }\n /** Attachment kinds accepted, checked against the sniffed content's\n * mime. Default: both (`['image', 'file']` — i.e. no restriction). */\n allowedKinds?: ChatAttachmentKind[]\n /** `<input accept>`-style gate for the file picker/drop/paste path.\n * Default {@link ATTACHMENT_ACCEPT}. */\n accept?: string\n /** When `false`, `addFiles` rejects every call via `onReject` (and\n * `blockReason` explains why) instead of staging anything — the\n * replacement for gtm's `workspaceId`-truthiness gate (e.g. no workspace\n * loaded yet). Default `true`. */\n enabled?: boolean\n}\n\n/** Provide staged file chips, ready attachments, and methods to add, retry, or drop composer files */\nexport interface UseComposerAttachmentsResult {\n /** Chip models for `ChatComposer`'s `pendingFiles` prop, one per staged\n * file — `kind` is always `'file'` (agent-app's `ComposerFile.kind`\n * discriminates file-vs-folder chips, not attachment media type). */\n composerFiles: ComposerFile[]\n /** Ready-to-send attachment descriptors — only files whose upload\n * succeeded, straight from the server's response (no recompute). Feed\n * this into `ChatTurnRequestPayload.attachments`. */\n references: ChatAttachmentInput[]\n /** Validate + stage + upload the given files, one request per file. */\n addFiles: (files: File[] | FileList) => Promise<void>\n /** Re-upload a failed entry using its retained `File`. */\n retry: (id: string) => void\n /** Drop one staged entry, aborting its upload and revoking its preview. */\n removeAttachment: (id: string) => void\n /** Forget every staged entry (call after a successful send). */\n clear: () => void\n /** True while any file is still pending or uploading. */\n hasPending: boolean\n /** True while any file failed to upload. */\n hasError: boolean\n /** Why a send is blocked, or `null` when the queue is clean. */\n blockReason: string | null\n}\n\nfunction newId(): string {\n const cryptoObject = globalThis.crypto\n if (typeof cryptoObject?.randomUUID === 'function') return cryptoObject.randomUUID()\n return `att-${Date.now()}-${Math.random().toString(36).slice(2)}`\n}\n\n/** Suffix a name (`report.pdf` → `report-2.pdf`) until it's unused. The\n * server writes to a name-derived store path, so identical names would\n * overwrite. The suffix stays inside the store-path charset (see\n * `sanitizeAttachmentFileName`). Ported byte-for-byte from gtm's\n * `dedupeName`. */\nfunction dedupeName(name: string, taken: Set<string>): string {\n if (!taken.has(name)) return name\n const dot = name.lastIndexOf('.')\n const base = dot > 0 ? name.slice(0, dot) : name\n const ext = dot > 0 ? name.slice(dot) : ''\n let n = 2\n let candidate = `${base}-${n}${ext}`\n while (taken.has(candidate)) {\n n += 1\n candidate = `${base}-${n}${ext}`\n }\n return candidate\n}\n\n/** `image/*` → `'image'`, everything else → `'file'`. Deliberately\n * reimplemented here (not imported from `../chat-store/parts`, which pulls\n * the drizzle-adjacent `/chat-store` barrel): this module must stay reachable\n * from a browser bundle with only the `/chat-routes` validation core as a\n * dependency. */\nfunction kindForMime(mime: string): ChatAttachmentKind {\n return mime.startsWith('image/') ? 'image' : 'file'\n}\n\n/** Pull a human-readable message out of the upload endpoint's error body.\n * Ported from gtm's `parseUploadError`: handles both `{ error: string }`\n * (size/count/access errors) and `{ error: { message } }` (the\n * `createAttachmentUploadRoute` envelope, `{error:{code,message,path?}}`). */\nasync function parseUploadError(res: Response): Promise<string> {\n const detail = await res.json().catch(() => null)\n if (detail && typeof detail === 'object' && 'error' in detail) {\n const error = (detail as { error: unknown }).error\n if (typeof error === 'string' && error) return error\n if (error && typeof error === 'object' && 'message' in error) {\n const message = (error as { message: unknown }).message\n if (typeof message === 'string' && message) return message\n }\n }\n return `Upload failed (${res.status})`\n}\n\n/** Shown when neither `uploadUrl` nor `buildUploadRequest` is configured\n * while `enabled` — a product wiring bug, not a user-facing rejection, so it\n * lands each affected entry in `error` (with `onError`) rather than blocking\n * `addFiles` outright via `onReject`: the files still stage and can be\n * retried once the product fixes its config, instead of silently vanishing. */\nconst NO_UPLOAD_TARGET_MESSAGE = 'No upload destination configured (pass uploadUrl or buildUploadRequest)'\n\n/**\n * Owns the composer's attachment lifecycle: validate selected/dropped/pasted\n * files against the shared limits, upload each accepted file to the\n * product's store (one request per file), and track every file's status so\n * the composer can render chips and gate sending.\n *\n * Failures surface loud — a rejected file calls `onReject` and is never\n * uploaded; a failed upload calls `onError` and leaves an error chip the user\n * can retry or remove. `references` only ever contains files whose upload the\n * server actually confirmed.\n */\nexport function useComposerAttachments(\n options: UseComposerAttachmentsOptions,\n): UseComposerAttachmentsResult {\n // Latest options, read from inside stable callbacks — avoids re-creating\n // `addFiles`/`upload` (and therefore breaking referential stability for\n // effects a host might hang off them) every time a caller passes a fresh\n // options object literal.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [staged, setStagedState] = useState<StagedAttachment[]>([])\n // Mirror of `staged` kept in lockstep so dedupe/aggregate-cap/abort read\n // current values synchronously (setState callbacks alone can't answer\n // \"what's staged right now\" mid-validation).\n const stagedRef = useRef<StagedAttachment[]>([])\n const controllersRef = useRef<Map<string, AbortController>>(new Map())\n\n // Post-unmount calls reduce to a React no-op setState; the refs they touch\n // die with the instance.\n const setStaged = useCallback(\n (updater: StagedAttachment[] | ((prev: StagedAttachment[]) => StagedAttachment[])) => {\n const next =\n typeof updater === 'function'\n ? (updater as (prev: StagedAttachment[]) => StagedAttachment[])(stagedRef.current)\n : updater\n stagedRef.current = next\n setStagedState(next)\n },\n [],\n )\n\n const upload = useCallback(\n async (id: string, file: File, name: string) => {\n const opts = optionsRef.current\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'uploading', errorMessage: undefined } : s)),\n )\n const controller = new AbortController()\n controllersRef.current.set(id, controller)\n const form = new FormData()\n form.append('file', file, name)\n\n const request = opts.buildUploadRequest\n ? opts.buildUploadRequest({ file, name, form })\n : opts.uploadUrl\n ? { url: opts.uploadUrl }\n : null\n\n if (!request) {\n setStaged((prev) =>\n prev.map((s) =>\n s.id === id ? { ...s, status: 'error', errorMessage: NO_UPLOAD_TARGET_MESSAGE } : s,\n ),\n )\n opts.onError?.(NO_UPLOAD_TARGET_MESSAGE)\n controllersRef.current.delete(id)\n return\n }\n\n try {\n const res = await fetch(request.url, {\n method: 'POST',\n credentials: 'same-origin',\n ...request.init,\n body: form,\n signal: controller.signal,\n })\n if (!res.ok) {\n const message = await parseUploadError(res)\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n const data = (await res.json()) as { files?: ChatAttachmentInput[] }\n const uploaded = data.files?.[0]\n if (!uploaded) {\n const message = 'Upload returned no file'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n return\n }\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'ready', reference: uploaded } : s)),\n )\n } catch (err) {\n if ((err as Error).name === 'AbortError') return // silent removal — see removeAttachment/clear\n const message =\n err instanceof Error && err.message ? err.message : 'Upload failed — check your connection'\n setStaged((prev) =>\n prev.map((s) => (s.id === id ? { ...s, status: 'error', errorMessage: message } : s)),\n )\n opts.onError?.(message)\n } finally {\n controllersRef.current.delete(id)\n }\n },\n [setStaged],\n )\n\n const addFiles = useCallback(\n async (files: File[] | FileList) => {\n const opts = optionsRef.current\n const enabled = opts.enabled ?? true\n if (!enabled) {\n opts.onReject?.('Attachments are disabled')\n return\n }\n\n const accept = opts.accept ?? ATTACHMENT_ACCEPT\n const maxCount = opts.limits?.maxCount ?? ATTACHMENT_MAX_COUNT\n const maxBinaryBytes = opts.limits?.maxBinaryBytes ?? MAX_BINARY_ATTACHMENT_BYTES\n const maxTextBytes = opts.limits?.maxTextBytes ?? MAX_TEXT_ATTACHMENT_BYTES\n const maxTotalBytes = opts.limits?.maxTotalBytes ?? MAX_ATTACHMENT_TOTAL_BYTES\n const allowedKinds = opts.allowedKinds ?? (['image', 'file'] as ChatAttachmentKind[])\n\n const list = Array.isArray(files) ? files : Array.from(files)\n\n // Pass 1: accept-list, then count cap — accept first, and the count\n // checked against what is already staged plus what this batch has taken.\n const currentCount = stagedRef.current.length\n const countAccepted: File[] = []\n for (const file of list) {\n if (!isAcceptedFileType(file, accept)) {\n opts.onReject?.(acceptRejectionReason(file, accept), file)\n continue\n }\n if (currentCount + countAccepted.length >= maxCount) {\n opts.onReject?.(`\"${file.name}\" was not added — the ${maxCount}-file limit is already reached.`, file)\n continue\n }\n countAccepted.push(file)\n }\n\n // Pass 2: real content sniff + type gate + per-kind size cap +\n // allowed-kinds gate — the SAME checks the server enforces, so a\n // rejection never differs depending on which side classified the bytes\n // first. Nothing here ever reaches the network.\n const sizeAccepted: File[] = []\n for (const file of countAccepted) {\n const bytes = new Uint8Array(await file.arrayBuffer())\n const sniff = sniffBinary(bytes)\n const typeCheck = checkAttachmentType(file.name, sniff)\n if (!typeCheck.succeeded) {\n opts.onReject?.(typeCheck.message, file)\n continue\n }\n const limit = sniff.binary ? maxBinaryBytes : maxTextBytes\n if (file.size > limit) {\n opts.onReject?.(attachmentSizeErrorMessage(file.name, file.size, limit), file)\n continue\n }\n const mediaType = sniff.mime ?? file.type ?? ''\n const kind = kindForMime(mediaType)\n if (!allowedKinds.includes(kind)) {\n opts.onReject?.(`\"${file.name}\" is a ${kind} attachment, which isn't accepted here`, file)\n continue\n }\n sizeAccepted.push(file)\n }\n\n // Pass 3: running aggregate cap across this batch + everything already\n // staged (any status) — a partial batch can still land.\n const accepted: File[] = []\n let totalBytes = stagedRef.current.reduce((total, s) => total + s.size, 0)\n for (const file of sizeAccepted) {\n const nextTotalBytes = totalBytes + file.size\n if (nextTotalBytes > maxTotalBytes) {\n opts.onReject?.(attachmentTotalSizeErrorMessage(nextTotalBytes, maxTotalBytes), file)\n continue\n }\n accepted.push(file)\n totalBytes = nextTotalBytes\n }\n if (accepted.length === 0) return\n\n // Stage under the name the server will actually store, so the chip and\n // the message's attachment references never diverge.\n const taken = new Set(stagedRef.current.map((s) => s.name))\n const entries: StagedAttachment[] = accepted.map((file) => {\n const name = dedupeName(sanitizeAttachmentFileName(file.name), taken)\n taken.add(name)\n return {\n id: newId(),\n file,\n name,\n size: file.size,\n status: 'pending',\n previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n })\n setStaged((prev) => [...prev, ...entries])\n for (const entry of entries) void upload(entry.id, entry.file, entry.name)\n },\n [setStaged, upload],\n )\n\n const retry = useCallback(\n (id: string) => {\n const entry = stagedRef.current.find((s) => s.id === id)\n if (!entry) return\n void upload(entry.id, entry.file, entry.name)\n },\n [upload],\n )\n\n const removeAttachment = useCallback(\n (id: string) => {\n controllersRef.current.get(id)?.abort()\n controllersRef.current.delete(id)\n const entry = stagedRef.current.find((s) => s.id === id)\n if (entry?.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n setStaged((prev) => prev.filter((s) => s.id !== id))\n },\n [setStaged],\n )\n\n const clear = useCallback(() => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n setStaged([])\n }, [setStaged])\n\n useEffect(\n () => () => {\n for (const controller of controllersRef.current.values()) controller.abort()\n controllersRef.current.clear()\n for (const entry of stagedRef.current) {\n if (entry.previewUrl) URL.revokeObjectURL(entry.previewUrl)\n }\n },\n [],\n )\n\n // `previewUrl` and `errorMessage` travel with the chip model. This hook is\n // the only place that knows either one — it mints the object URL and holds\n // the upload's failure text — so a projection that dropped them left the\n // composer rendering a red chip with no reason on it and no thumbnail for a\n // staged image.\n const composerFiles = useMemo<ComposerFile[]>(\n () =>\n staged.map((s) => ({\n id: s.id,\n name: s.name,\n size: s.size,\n kind: 'file' as const,\n status: s.status,\n previewUrl: s.previewUrl,\n errorMessage: s.errorMessage,\n })),\n [staged],\n )\n\n const references = useMemo<ChatAttachmentInput[]>(\n () =>\n staged\n .filter((s): s is StagedAttachment & { reference: ChatAttachmentInput } => s.status === 'ready' && !!s.reference)\n .map((s) => s.reference),\n [staged],\n )\n\n const hasPending = useMemo(\n () => staged.some((s) => s.status === 'pending' || s.status === 'uploading'),\n [staged],\n )\n const hasError = useMemo(() => staged.some((s) => s.status === 'error'), [staged])\n const enabled = options.enabled ?? true\n const blockReason = !enabled\n ? 'Attachments are disabled'\n : hasPending\n ? 'Attachments are still uploading'\n : hasError\n ? 'Remove failed attachments to send'\n : null\n\n return {\n composerFiles,\n references,\n addFiles,\n retry,\n removeAttachment,\n clear,\n hasPending,\n hasError,\n blockReason,\n }\n}\n","/**\n * Per-harness brand marks for the canonical pickers — the same marks the\n * legacy sandbox-ui harness picker (`dashboard/harness-logo.tsx`) shipped,\n * vendored as inline SVG so `/web-react` stays dependency-free beyond React:\n * sandbox-ui (and its `@lobehub/icons-static-svg` bundle) is an OPTIONAL peer\n * the canonical pickers must not force on a consumer. Geometry is the lobehub\n * single-color artwork, rendered in `currentColor` exactly as the legacy\n * component painted it (a foreground-filled CSS mask), so every mark tracks\n * the theme. Harnesses with no published brand mark get an honest inline\n * lucide glyph — bot / plug / terminal, the same fallbacks the legacy picker\n * used — and an unknown id falls back to the neutral bot. Data-record\n * structure mirrors `./provider-logo`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { Harness } from '../harness'\n\nexport interface HarnessGlyphProps {\n /** Harness to mark. Typed as the canonical union; an out-of-union runtime\n * value still renders — it gets the neutral fallback glyph. */\n harness: Harness\n className?: string\n}\n\n// ── brand marks (single-color lobehub artwork, fill) ──────────────────────\n\nconst BRAND_PATHS: Partial<Record<Harness, readonly string[]>> = {\n opencode: [\n 'M16 6H8v12h8V6zm4 16H4V2h16v20z',\n ],\n 'claude-code': [\n 'M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z',\n ],\n codex: [\n 'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z',\n ],\n amp: [\n 'M15.087 23.18L12.03 24l-2.097-7.823-5.738 5.738-2.251-2.251 5.718-5.719-7.769-2.082.82-3.057 11.294 3.08 3.08 11.295z',\n 'M19.505 18.762l-3.057.82-2.564-9.573-9.572-2.564.819-3.057 11.295 3.079 3.08 11.295z',\n 'M23.893 14.374l-3.057.82-2.565-9.572L8.7 3.057 9.52 0l11.295 3.08 3.079 11.294z',\n ],\n 'kimi-code': [\n 'M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z',\n ],\n openclaw: [\n 'M9.046 7.104a.527.527 0 110 1.055.527.527 0 010-1.055z',\n 'M15.376 7.104a.528.528 0 110 1.056.528.528 0 010-1.056z',\n 'M16.877 1.912c.58-.27 1.14-.323 1.616-.037a.317.317 0 01-.326.542c-.227-.136-.547-.153-1.022.068-.352.165-.765.45-1.234.866 2.683 1.17 4.4 3.5 5.148 5.921a6.421 6.421 0 00-.704.184c-.578.016-1.174.204-1.502.735-.338.55-.268 1.276.072 2.069l.005.012.007.014c.523 1.045 1.318 1.91 2.2 2.284-.912 3.274-3.44 6.144-5.972 6.988v2.109h-2.11v-2.11c-1.043.417-2.086.01-2.11 0v2.11h-2.11v-2.11c-2.531-.843-5.061-3.713-5.973-6.987.882-.373 1.678-1.238 2.2-2.284l.007-.014.006-.012c.34-.793.41-1.518.071-2.069-.327-.531-.923-.719-1.503-.735a6.409 6.409 0 00-.704-.183c.749-2.421 2.466-4.751 5.149-5.922-.47-.416-.88-.701-1.234-.866-.474-.221-.794-.204-1.021-.068a.318.318 0 01-.435-.109.317.317 0 01.109-.433c.476-.286 1.036-.233 1.615.037.49.229 1.031.628 1.621 1.182A9.924 9.924 0 0112 2.568c1.199 0 2.284.19 3.256.526.59-.554 1.13-.953 1.62-1.182zM8.835 6.577a1.266 1.266 0 100 2.532 1.266 1.266 0 000-2.532zm6.33 0a1.267 1.267 0 100 2.533 1.267 1.267 0 000-2.533z',\n 'M.395 13.118c-.966-1.932-.163-3.863 2.41-3.365v-.001l.05.01c.084.018.17.038.26.06.033.009.067.017.1.027.084.022.168.048.255.076l.09.027c.528 0 .95.158 1.16.501.212.343.212.87-.105 1.61-.085.17-.178.333-.276.489l-.01.017a4.967 4.967 0 01-.62.791l-.019.02c-1.092 1.117-2.496 1.336-3.295-.262z',\n 'M21.193 9.753c2.574-.5 3.378 1.433 2.411 3.365-.58 1.159-1.476 1.361-2.342.96l-.011-.005a2.419 2.419 0 01-.114-.056l-.019-.01a2.751 2.751 0 01-.115-.067l-.023-.014c-.035-.022-.071-.044-.106-.068l-.05-.035c-.55-.388-1.062-1.007-1.44-1.76-.276-.647-.311-1.132-.174-1.472.176-.439.636-.639 1.23-.639.032-.011.066-.02.099-.03.08-.026.16-.05.238-.072l.117-.03a5.502 5.502 0 01.3-.067z',\n ],\n hermes: [\n 'M5.938 12.835c.127-.039.285.02.373.143.028.038.036.092.046.14.003.014-.02.033-.04.05-.124-.098-.24-.194-.354-.291-.011-.01-.016-.027-.025-.042zM8.396 9.412c.195-.032.39-.06.588-.05a.54.54 0 01.148.026c.202.071.402.147.601.224.028.01.05.036.075.055l-.013.027a9.203 9.203 0 01-.26-.089c-.115-.038-.213-.077-.315-.098-.25-.05-.25-.046-.292-.014l.574.144c.275.139.55.276.823.417.042.022.09.057.107.098.026.06.063.076.117.072.066-.006.132-.017.213-.027l-.04.086c.051.08.142.02.216.064-.074.13-.247.09-.334.199l.061.074-.12.087c0 .106-.038.168-.306.243l.026.085-.196.042.07.124h-.25l-.007.137c-.081-.01-.161-.018-.244-.027l-.053.123c-.027-.008-.052-.011-.073-.023-.067-.038-.128-.056-.195.006-.019.017-.063.014-.093.008-.026-.006-.05-.029-.07-.042-.11.095-.11.095-.208.003-.057.046-.12.074-.186.011-.063.027-.123-.02-.178-.014-.07.007-.097-.035-.133-.07l-.13.033c-.013-.236-.194-.19-.34-.203.005-.072.05-.092.095-.094a.474.474 0 01.159.022c.164.05.32.12.496.138.203.021.405.029.601-.015.265-.059.52-.149.707-.365.049-.056.083-.127.117-.195.019-.038.02-.084-.02-.116a1.397 1.397 0 00-.382-.217c.024.12-.031.182-.115.221 0 .014-.004.025 0 .03.08.115.084.16-.007.267a1.39 1.39 0 01-.218.211.477.477 0 01-.641-.05 1.36 1.36 0 01-.133-.152c-.078-.107-.076-.108-.033-.236-.165-.08-.128-.226-.104-.364.008-.05.028-.096.049-.163-.04.014-.067.017-.087.032a.897.897 0 00-.316.357c-.007.016-.01.034-.02.047-.012.015-.034.038-.045.035-.02-.006-.037-.027-.05-.045-.008-.012-.007-.032-.012-.057h-.126l.053-.172a14.82 14.82 0 00-.039-.049l.11-.284c-.06.026-.091.044-.124.051-.03.007-.064 0-.095 0 0-.031-.01-.07.004-.092.149-.22.305-.428.593-.476z',\n 'M8.06 10.788c-.003-.038-.004-.075.037-.062.016.006.034.048.028.067-.01.04-.038.032-.064-.005z',\n 'M11.981.009c.226-.012.453-.011.679 0 .247.01.495.024.74.062.401.064.798.157 1.19.273.463.138.92.299 1.356.511a7.31 7.31 0 012.948 2.642c.292.469.536.963.739 1.479.219.556.446 1.11.623 1.683.204.654.329 1.326.458 1.997.097.504.182 1.01.29 1.511.156.722.329 1.44.494 2.16.186.812.4 1.615.63 2.415.102.355.193.713.282 1.072.11.436.202.876.254 1.323.031.278.066.557.073.837a7.56 7.56 0 01-.017.88c-.037.413-.1.818-.226 1.212a5.017 5.017 0 01-.915 1.649l-.13.156.018.023c.043-.023.088-.041.127-.068.2-.138.373-.307.531-.49.4-.46.721-.973.975-1.529a3.59 3.59 0 00.325-1.72c-.024-.424-.097-.834-.3-1.213-.013-.027-.015-.06-.03-.121.05.035.082.048.101.072.107.13.22.258.315.398.33.494.46 1.052.486 1.64a3.75 3.75 0 01-.47 1.97c-.36.655-.887 1.14-1.526 1.506-.193.111-.394.21-.595.308-.157.078-.248.211-.318.365a.522.522 0 00-.033.406.359.359 0 01.013.139c-.005.077-.077.155-.14.162-.054.006-.125-.043-.15-.116a1.206 1.206 0 01-.06-.233c-.04-.314-.155-.6-.308-.87a3.906 3.906 0 00-.73-.91 2.129 2.129 0 00-.897-.524 4.093 4.093 0 00-.692-.131c-.075-.008-.15-.04-.22.01.18.06.363.11.538.18.434.173.82.43 1.18.728.308.255.58.543.794.884.098.155.186.315.227.496.027.123.042.25.067.375.013.062-.002.109-.053.144-.047.033-.122.034-.163-.01a.455.455 0 01-.08-.14c-.03-.073-.038-.159-.078-.225a7.314 7.314 0 00-1.423-1.664c-.16-.137-.329-.26-.537-.323-.376-.114-.753-.203-1.15-.154-.213.025-.427.032-.64.053a1.6 1.6 0 00-.736.278 5.14 5.14 0 00-.834.72c-.329.342-.642.699-.955 1.055-.136.155-.264.319-.314.531a5.227 5.227 0 00-.012.051.096.096 0 01-.09.076h-.31c-.046 0-.082-.048-.072-.094.023-.108.045-.216.07-.324.075-.325.19-.635.368-.917.024-.039.04-.088.104-.08l.01.049.027.077c.28-.435.571-.834.996-1.135.283-.204.584-.378.89-.55a.196.196 0 00-.098-.002c-.162.043-.325.084-.485.134-.402.124-.764.33-1.11.566-.147.1-.298.193-.414.333a7.314 7.314 0 00-1.07 1.767.845.845 0 00-.04.12.075.075 0 01-.072.056h-.494c-.04 0-.062-.051-.036-.082.123-.14.246-.282.377-.415.275-.281.58-.532.777-.884.027-.048.063-.09.095-.135.238-.333.54-.607.818-.902.082-.086.175-.16.26-.24.029-.027.053-.057.079-.085l-.018-.025-.135.041c-.034.017-.07.031-.102.05-.248.144-.494.292-.743.433-.408.23-.825.439-1.209.711-.281.2-.591.358-.889.533-.02.012-.044.015-.08.028-.015-.135.143-.201.108-.336-.033.014-.064.02-.085.038-.111.096-.227.19-.328.296-.148.157-.284.325-.425.488-.125.143-.25.286-.373.431A.153.153 0 019.89 24H8.762a.316.316 0 00.016-.042c.028-.09.085-.172.083-.28-.091-.018-.162.001-.212.077a4.45 4.45 0 00-.136.215c-.01.016-.024.03-.042.03h-.093c-.019 0-.029-.022-.017-.037.071-.088.14-.178.209-.268.001-.002-.006-.012-.012-.024-.014.004-.03.006-.045.013-.176.09-.352.181-.527.274a.363.363 0 01-.168.042H5.202c-.026 0-.039-.036-.019-.053.21-.178.402-.374.558-.605.335-.496.538-1.047.667-1.629.004-.02-.003-.043-.006-.091-.037.048-.059.072-.076.1a1.943 1.943 0 01-.334.415c-.28.258-.59.448-.983.464-.297.012-.588 0-.865-.127-.46-.21-.722-.57-.794-1.072-.025-.17-.017-.171-.182-.219A3.513 3.513 0 011.97 20.6a2.286 2.286 0 01-.808-1.13 3.569 3.569 0 01-.16-1.245c.002-.034.016-.067.024-.1.032.023.046.043.05.066.033.153.059.308.096.46.086.355.257.664.516.92.258.256.571.419.91.532.358.118.717.138 1.07-.016a1.89 1.89 0 00.621-.452c.328-.348.533-.76.648-1.223.009-.034.005-.071.007-.11-.015.006-.026.006-.03.011-.031.05-.064.1-.093.152-.284.502-.679.887-1.196 1.135-.351.17-.718.255-1.11.159a1.607 1.607 0 01-.971-.64 2.006 2.006 0 01-.368-.924 2.903 2.903 0 01.02-.886c.05-.439.466-1.17.742-1.271-.02.063-.035.112-.053.16-.043.116-.097.227-.13.345a1.901 1.901 0 00-.05.82c.033.212.09.416.204.6.147.236.346.407.62.465.11.023.225.014.338.018a.576.576 0 00.386-.131c.164-.128.282-.292.366-.481.168-.375.24-.777.309-1.179.05-.296.093-.594.133-.893.039-.281.071-.563.104-.845.026-.232.048-.464.074-.696.024-.228.052-.455.076-.683.024-.227.047-.455.069-.683.013-.14.022-.28.034-.42l.037-.417c.022-.25.041-.5.065-.748.008-.082-.02-.132-.09-.177a2.46 2.46 0 01-.492-.418c-.1-.109-.188-.228-.282-.342-.035-.042-.056-.097-.116-.118a2.084 2.084 0 00.275.597c.06.092.131.176.196.265.063.086.182.115.234.226-.028.003-.046.01-.06.006a4.74 4.74 0 01-.22-.057 2.71 2.71 0 01-1.287-.819c-.435-.487-.656-1.076-.71-1.723a5.206 5.206 0 01.014-1.06c.072-.602.22-1.186.45-1.745.155-.376.338-.741.526-1.102.205-.393.466-.75.765-1.076.512-.559 1.104-1.024 1.726-1.448.717-.49 1.478-.898 2.277-1.233C8.244.828 8.767.632 9.31.494c.655-.166 1.31-.33 1.982-.415.229-.03.458-.058.688-.07zm-1.847 22.82c-.07.06-.147.111-.207.18-.238.27-.464.549-.668.869l-.044.108a.177.177 0 00.093-.057c.174-.19.351-.378.519-.574.104-.122.195-.255.288-.386.024-.034.03-.08.046-.12l-.027-.02zm1.65-3.695a5.51 5.51 0 00-.653.593l-.37.386a.963.963 0 01-.377.25 1.372 1.372 0 01-.467.09c-.044 0-.087.006-.151.012.028.058.043.097.064.131.15.242.301.482.45.724.136.22.276.438.399.666.068.125.105.267.156.404.077.027.14-.018.202-.048.29-.135.579-.274.867-.412.213-.101.437-.186.636-.31.347-.215.68-.455 1.018-.685.015-.01.026-.028.042-.046-.023-.019-.038-.037-.056-.044-.287-.111-.527-.3-.77-.482a5.319 5.319 0 01-.506-.42 1.757 1.757 0 01-.41-.653c-.019-.049-.045-.095-.075-.156zm-5.847.264c-.06.096-.097.194-.132.293a3.38 3.38 0 01-.555 1.01c-.2.25-.455.412-.762.493-.23.06-.464.076-.7.07-.048-.002-.097.002-.158.005.016.04.021.066.035.085.1.145.23.246.4.295.157.046.316.034.498.023.181-.037.343-.115.485-.234.238-.199.402-.454.536-.732.175-.363.264-.751.342-1.144.01-.053.008-.11.011-.164zm14.945-4.586c.008.029.016.057.027.107.024.155.051.31.072.464.03.219.067.437.078.657.017.344.027.689-.014 1.033-.037.315-.063.633-.116.946a6.153 6.153 0 01-.46 1.518c-.008.018-.01.039-.02.082.047-.03.077-.042.098-.064.085-.083.17-.167.248-.255.271-.305.458-.66.596-1.043.18-.498.228-1.011.145-1.531-.103-.65-.33-1.263-.597-1.881a9.055 9.055 0 00-.024-.055l-.033.022zM5.797 8.29a.26.26 0 00.018.153c.124.251.25.501.379.75.025.049.066.09.03.163-.284.06-.578.119-.88.255.059.038.097.06.132.087.042.032.112.058.09.12-.01.033-.075.048-.117.072.017.01.043.021.067.036.166.102.33.207.447.368.138.192.229.404.188.644-.079.469-.306.85-.69 1.132-.054.04-.106.083-.161.122a.243.243 0 00-.103.245.77.77 0 00.055.195c.083.196.22.35.375.492.083.076.159.164.222.257a.37.37 0 01.025.377c-.023.05-.05.099-.076.148-.03.06-.028.111.022.162.041.042.08.089.112.138.038.058.078.079.147.05a.486.486 0 01.333-.006c.16.046.302.126.444.21.13.077.264.149.4.219.067.035.14.05.219.026.071-.022.124.01.145.076.02.064-.003.108-.074.139-.07.03-.137.063-.209.088-.1.035-.201.073-.314.077-.013-.107.11-.088.127-.159-.206-.126-.643-.145-.801-.034.063.112.035.21-.096.313-.13-.1-.025-.202.002-.3a.209.209 0 00-.249.17c-.015.101.067.216.178.224.108.007.218-.005.326-.012.06-.005.12-.027.199 0-.103.123-.248.127-.357.19.002.05.07.086.019.131-.053.048-.095-.001-.132-.03-.08-.063-.16-.126-.231-.197a.474.474 0 01-.157-.311.52.52 0 00-.043-.172c-.032-.074-.032-.137.033-.19-.018-.03-.028-.053-.045-.072a1.222 1.222 0 01-.196-.369c-.053-.137-.046-.264.048-.381.024-.03.05-.06.064-.095a.664.664 0 00.047-.168c.017-.165-.064-.287-.182-.387-.186-.156-.36-.322-.46-.551-.005-.011-.024-.017-.037-.026-.011.017-.024.027-.025.038-.019.185-.045.37-.052.557-.014.377.058.743.162 1.104.118.41.289.798.488 1.173.267.502.537 1.002.812 1.5.055.098.13.189.208.27.198.202.452.272.724.273.202 0 .404-.006.605-.026.295-.03.59-.073.884-.113.183-.025.365-.057.548-.08.21-.026.38.073.522.21.16.156.305.327.447.5.22.265.397.56.554.867.05.098.07.1.147.03.13-.121.26-.242.394-.36.067-.059.088-.12.067-.213a3.535 3.535 0 01-.085-.796c.002-.157.006-.314.018-.471.015-.224.03-.45.06-.672a59.114 59.114 0 01.362-2.298c.087-.493.182-.984.268-1.477.06-.347.118-.694.162-1.043.034-.273.055-.55.063-.825.011-.332.003-.665.002-.998 0-.077.004-.155-.01-.23-.028-.142-.01-.155-.162-.19a5.826 5.826 0 00-.607-.107c-.146-.018-.207-.053-.221-.19-.006-.049-.025-.098-.041-.146-.009-.025-.024-.048-.046-.09l-.025.264c-.009.096-.029.116-.127.115-.055 0-.11-.008-.164-.008-.476 0-.952-.008-1.426.032-.095.008-.173-.015-.226-.103-.04-.066-.088-.126-.134-.186-.063-.084-.086-.093-.182-.06-.195.068-.388.138-.582.21a2.71 2.71 0 00-.675.394.986.986 0 01-.323.168c-.033.01-.07.008-.127.013.02-.066.024-.114.047-.15.064-.105.135-.205.205-.306.023-.033.049-.063.073-.095l-.015-.023-.201.037c-.146.04-.296.07-.437.122-.148.053-.266.023-.386-.072a3.623 3.623 0 01-.733-.786l-.093-.132zm8.592 8.963l-.147.09c-.22.134-.44.266-.659.402-.093.058-.184.12-.27.188-.085.07-.124.161-.072.272.047.1.093.2.147.294.047.08.124.138.213.147.11.01.228.012.336-.012.217-.05.372-.205.528-.357a.291.291 0 00.087-.308c-.046-.18-.079-.365-.118-.547-.011-.052-.027-.103-.045-.169zm-.257-2.409c-.12.291-.205.597-.325.91-.151.433-.294.87-.435 1.323.036-.01.054-.01.067-.018.261-.16.522-.324.785-.484.054-.033.071-.078.065-.138-.012-.13-.024-.262-.034-.393l-.068-.886c-.008-.103-.02-.206-.029-.31-.009 0-.017-.002-.026-.004zm3.081-8.13l.099.285c.08.231.159.463.24.714l.58 1.952c.187.63.372 1.262.558 1.893.114.382.235.762.343 1.146.072.257.126.519.186.799.044.206.087.413.127.64.034.106.023.226.077.325l.025-.006-.068-.362c-.038-.206-.077-.412-.113-.638-.015-.07-.029-.141-.046-.211-.095-.396-.177-.796-.29-1.187-.196-.685-.413-1.364-.618-2.046-.165-.549-.322-1.1-.488-1.648-.069-.227-.15-.45-.226-.695l-.117-.336c-.037-.107-.075-.216-.115-.322-.04-.106-.084-.21-.127-.314a7.558 7.558 0 01-.027.01zM6.225 14.304c-.063-.001-.115.014-.134.083a.35.35 0 00.41.012 4.533 4.533 0 00-.276-.095zM5.23 11.98c-.026-.027-.057-.048-.075.002-.012.032-.007.07-.01.113.082-.037.082-.037.085-.115zm.062-1.189a.135.135 0 00-.088.056.197.197 0 00-.025.11c.005.152.01.306.026.457a.751.751 0 00.066.218c.061.136.157.167.288.101.055-.027.06-.054.025-.11a4.52 4.52 0 01-.129-.211c-.015-.068-.066-.131-.033-.207.04-.09-.076-.116-.074-.19V10.874c-.003-.038-.006-.087-.056-.083zm-.017-.968a.867.867 0 00-.467.127c-.076.045-.084.07-.05.158.034.087.07.173.115.254.064.117.09.125.21.077a.657.657 0 01.336-.053c.202.022.357.136.504.264l.092.077c.007-.006.014-.013.022-.018-.019-.105-.035-.226-.149-.264-.157-.053-.324-.075-.508-.117l-.24-.005c.24-.169.452-.044.687.009-.063-.115-.153-.147-.23-.193-.082-.05-.17-.092-.25-.144-.06-.037-.12-.08-.072-.172zm10.233.325c-.23-.01-.427.08-.608.211-.034.026-.06.065-.105.117.087.026.15.046.232.065.044-.015.088-.03.13-.046.306-.114.61-.115.904.031.126.063.237.04.366-.005-.02-.031-.03-.054-.045-.071a.986.986 0 00-.448-.273c-.14-.044-.284-.024-.426-.03zM7.99 6.483a.308.308 0 00.002.133c.08.321.156.643.242.962.104.387.27.75.456 1.103.02.037.061.08.098.087a.404.404 0 00.253-.051l-.472-.84c-.23-.448-.405-.92-.579-1.394zM10.397.497c-.2-.008-.405.004-.603.034-.236.035-.47.087-.7.152-.287.08-.569.18-.852.273-.04.013-.074.038-.11.058.028.014.05.018.07.014.287-.068.58-.085.873-.09.134-.002.269.009.402.025.19.024.382.048.57.09.456.104.874.3 1.265.556.464.306.888.66 1.257 1.078.205.232.395.475.56.739.17.274.315.561.449.856.273.601.456 1.232.6 1.876.04.173.07.348.1.524.017.104.065.167.17.19.122.028.2.105.22.251-.003.102-.06.174-.129.24a1.065 1.065 0 00-.268.358.164.164 0 00.083-.039c.08-.086.162-.172.235-.265a.56.56 0 00.13-.333c.009-.05.022-.1.024-.15.007-.124-.017-.15-.143-.168-.025-.004-.049-.014-.073-.015-.082-.007-.125-.063-.137-.131-.033-.198-.004-.355.247-.408.086-.018.174-.03.26-.042.158-.023.315-.053.473-.067.14-.012.19.033.226.167.008.029.018.057.021.087.019.179-.008.225-.141.288-.027.013-.055.024-.078.042a.148.148 0 00-.051.067c-.039.144.073.382.206.445l.673.32c.023.011.05.015.075.023l.018-.026c-.015-.008-.032-.013-.044-.024a2.27 2.27 0 00-.544-.32 4.898 4.898 0 00-.173-.075.203.203 0 01-.126-.191c-.003-.085.045-.154.128-.187l.059-.025c.099-.044.118-.076.112-.187a.384.384 0 00-.008-.063c-.067-.294-.123-.59-.205-.88a9.478 9.478 0 00-.826-2.036 7.465 7.465 0 00-1.39-1.805 4.536 4.536 0 00-1.177-.824 3.656 3.656 0 00-1.016-.328 6.155 6.155 0 00-.712-.074zm6.719 5.955c.01.014.018.028.038.034l-.022-.044-.016.01zM4.103 3.917a.062.062 0 01-.03.012.455.455 0 01-.04.039c-.01.01-.02.02-.045.04l-.363.354c-.088.085-.17.178-.266.253-.284.22-.425.53-.544.855a.132.132 0 00-.007.071c.013.055.033.108.052.168l.074.026c-.017.056-.03.105-.047.152-.058.164-.118.327-.175.491-.005.015.008.036.019.077.08-.175.158-.33.225-.489.228-.544.484-1.074.819-1.561.09-.133.182-.266.283-.401.004-.006.007-.013.022-.03.001-.016.003-.032.015-.04l.008-.017zm12.976 2.408a.023.023 0 01.009.019.073.073 0 00-.006.01.188.188 0 00.007.02l.018.022c.002-.007.007-.016.005-.021-.003-.01-.012-.018-.02-.038a1.331 1.331 0 01-.013-.012zM4.199 4.48c-.003.004-.008.008-.027.014-.005.013-.011.025-.031.047a2.085 2.085 0 01-.124.167c-.048.07-.116.055-.181.041-.134-.028-.228.016-.287.143-.089.187-.187.37-.273.56-.049.108-.11.216-.118.36.081.003.154.007.228.008h.228a2.563 2.563 0 01-.079.264c-.01.052-.022.103-.033.155l.02.004c.018-.046.037-.092.067-.153.066-.142.13-.285.2-.426.02-.04.034-.1.116-.092 0 .043.004.084 0 .124-.005.045-.017.09-.028.143.141.043.086.174.115.269.102-.022.104-.195.248-.144v.205l.017.002.439-1.059c-.13 0-.246-.02-.358.033-.024.011-.058-.001-.108-.004.075-.15.139-.278.211-.417a.128.128 0 01.025-.036c0-.015-.001-.03.008-.038l.006-.02c-.005.006-.01.011-.028.017-.004.012-.009.024-.026.045a.085.085 0 01-.032.033c-.123.157-.09.164-.258.106-.079-.027-.078-.028-.047-.144.028-.046.056-.093.098-.15 0-.016-.001-.032.007-.042L4.2 4.48zm2.073-.67c-.003.006-.007.011-.027.016-.094.125-.194.246-.28.377-.155.238-.301.481-.451.723-.14.224-.345.368-.575.481-.017.008-.04.006-.079.011.012-.059.016-.109.033-.153a6.076 6.076 0 01.229-.518l-.007-.02a.138.138 0 01-.035.025c-.028.05-.055.1-.093.164-.26.424-.443.817-.442.95.024.004.048.011.073.013.177.013.188.007.26-.165.03-.07.077-.12.147-.15l.175-.07c.044-.018.085-.057.146-.032.003.05-.01.11.014.145.042.062.044.125.047.193.002.049.017.098.026.147.029-.034.039-.065.05-.097.142-.39.277-.782.428-1.17.1-.256.22-.504.33-.756.013-.03.013-.067.03-.092V3.81zm3.987-.34c0 .045.01.084.021.123.042.16.094.318.124.48.024.133.023.27.028.406 0 .033-.019.067-.032.11-.094-.058-.047-.158-.106-.215h-.125c-.015.072-.01.152-.046.2-.066.085-.155.154-.236.227-.043.038-.078.018-.103-.025l-.046-.087c-.065.035-.117.069-.172.093-.116.051-.235.095-.35.147-.085.038-.09.053-.07.147.014.075.034.148.047.223.013.072.05.109.123.124.233.05.462.115.657.265.058-.102.058-.102.168-.151.03-.014.06-.03.092-.042.08-.03.115-.017.15.06.023.048.041.098.066.158.06-.14-.042-.267.017-.416.157.18.24.39.375.567a.235.235 0 00.022-.098c.002-.124 0-.247.002-.371 0-.034.013-.067.02-.1l.032-.003c.11.155.13.354.226.52a3.036 3.036 0 00-.01-.392c-.004-.045 0-.074.05-.088.08.036.116.14.215.158-.03-.275-.423-1.137-.798-1.635-.114-.127-.2-.28-.34-.386zm-2.667.696c-.019.034-.03.05-.037.067-.061.185-.125.37-.18.556-.031.105-.087.169-.195.19-.09.019-.178.052-.268.073-.038.009-.089.015-.118-.003-.024-.016-.025-.069-.036-.106-.064.076-.082.087-.17.047-.133-.062-.262-.135-.393-.201-.048-.025-.093-.063-.17-.03-.043.12-.091.25-.137.382-.099.28-.087.242.095.453.046.048.102.03.154.023.054-.009.106-.03.16-.036.13-.013.26-.08.367-.015.204-.064.387-.122.571-.178.05-.015.089.005.114.054.022.042.034.093.082.121.038-.056-.013-.128.063-.178l.14.241-.042-1.46zm.278.358c-.096-.01-.107.01-.11.108-.002.038-.003.078.002.115.03.2.099.386.174.57.002.006.012.01.022.015l.078-.05c.052.036.081.088.153.088.205-.002.41.014.616.012.099-.001.158.042.205.12.018.03.024.077.088.066l-.08-.394c-.05-.195-.085-.395-.172-.589-.057.057-.114.068-.18.046a.72.72 0 00-.135-.028c-.22-.028-.44-.059-.66-.08zm10.254-1.727c.089.163.155.316.139.491-.016.168.026.342-.044.516-.047-.033-.088-.082-.112-.075-.117.035-.164-.057-.227-.115a4.772 4.772 0 01-.286-.29l-.104-.113a4.856 4.856 0 01-.023.019c.035.046.07.093.11.156.04.064.084.127.122.193.034.058.065.118.031.205-.082-.01-.164-.019-.246-.032-.06-.01-.101 0-.124.07-.031.098-.037.096-.15.09.02.042.036.08.057.116.041.074.03.138-.03.196-.06.06-.118.122-.178.181a.175.175 0 01-.185.046c-.222-.061-.447-.113-.67-.174-.032-.009-.063-.04-.086-.068-.03-.04-.052-.087-.08-.13-.044-.07-.09-.138-.136-.207a.18.18 0 00-.014.105c.012.127.03.253.035.38.005.1-.024.12-.121.104-.104-.017-.206-.04-.31-.058-.064-.012-.131-.028-.202.03l.081.208c.09 0 .166-.01.237.002a.819.819 0 01.458.251c.078.083.154.168.241.26l.018-.005c-.004-.006-.008-.013-.01-.04.014-.056-.062-.118.018-.178.031.03.064.057.088.09.058.078.111.159.169.257l.089.141.024-.013a2093.819 2093.819 0 01-.427-.934c.055.007.083.007.108.016.193.07.385.142.577.216.074.028.147.06.219.094.062.028.112.018.157-.033.05-.056.102-.112.154-.167.05-.051.095-.046.132.014.016.025.026.053.04.08.071.138.143.277.217.433l.159.308.025-.011c-.044-.106-.07-.218-.138-.334-.057-.182-.168-.346-.206-.545.136.034.362.326.567.732l.057.074.018-.011a1.563 1.563 0 01-.052-.127c-.046-.145-.097-.29-.136-.436-.022-.083-.036-.173.022-.26l.109.058-.026-.207.027-.016c.022.02.05.036.065.06.073.108.143.22.215.33.01.016.029.029.043.043-.036-.217-.2-.38-.229-.626l.155.112c.014-.166.012-.319.042-.465.032-.158-.023-.297-.063-.445.024.004.036.006.055.025.092.124.183.249.277.371.02.027.05.047.069.087l.04.063.019-.015a.293.293 0 01-.053-.082 27.922 27.922 0 01-.332-.49c-.221-.311-.363-.467-.485-.521zm-6.57.327c-.003.161.092.275.069.415l-.368.087c.09.139.032.237-.052.331-.05.057-.092.122-.143.178-.037.04-.046.078-.018.126l.16.275c.029.048.072.066.128.064.076-.003.152 0 .228-.001.116-.003.216.022.275.137.006.014.02.024.044.052.004-.059-.003-.098.01-.13.016-.04.04-.099.072-.108.084-.023.173-.024.26-.03.013-.001.027.018.04.029l.071.065c.019-.11-.082-.198-.024-.31l.126.04c-.026-.123-.07-.245-.071-.366 0-.123.051-.243.115-.36.107.062.16.156.234.253.183.265.36.533.494.834.165-.078.27.068.407.088-.003-.106-.133-.441-.197-.492a.142.142 0 00-.102-.028c-.06.011-.119.039-.191.063-.025-.039-.056-.078-.077-.122a3.936 3.936 0 00-.473-.783c-.076-.094-.16-.182-.228-.26l-.391.285c-.049.035-.094.03-.132-.017l-.169-.207c-.025-.03-.053-.059-.097-.108z',\n ],}\n\n// ── honest fallbacks for harnesses with no published brand mark ───────────\n// Inline lucide paths (v1.27, ISC) — `/web-react` ships no icon-library\n// dependency, so the stroke glyphs here match the set in `./controls`.\n\nfunction BotGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 8V4H8\" />\n <rect width=\"16\" height=\"12\" x=\"4\" y=\"8\" rx=\"2\" />\n <path d=\"M2 14h2\" />\n <path d=\"M20 14h2\" />\n <path d=\"M15 13v2\" />\n <path d=\"M9 13v2\" />\n </svg>\n )\n}\n\nfunction PlugGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 22v-5\" />\n <path d=\"M15 8V2\" />\n <path d=\"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z\" />\n <path d=\"M9 8V2\" />\n </svg>\n )\n}\n\nfunction TerminalGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 19h8\" />\n <path d=\"m4 17 6-6-6-6\" />\n </svg>\n )\n}\n\n/** Lucide fallback per harness without a brand mark — the same assignments\n * the legacy picker shipped (`factory-droids`→bot, `nanoclaw`→plug,\n * `cli-base`→terminal). */\nconst FALLBACK_GLYPHS: Partial<Record<Harness, (props: { className?: string }) => ReactNode>> = {\n 'factory-droids': BotGlyph,\n nanoclaw: PlugGlyph,\n 'cli-base': TerminalGlyph,\n}\n\n/**\n * Brand mark for a harness — size it from the call site (`className=\"h-4\n * w-4\"`). Unknown ids render the neutral bot, never an invented logo.\n * `data-glyph` names the resolved mark so tests and stories can assert\n * brand-vs-fallback without snapshotting path data.\n */\nexport function HarnessGlyph({ harness, className }: HarnessGlyphProps): ReactNode {\n const brand = BRAND_PATHS[harness]\n if (brand) {\n return (\n <svg\n className={className}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n role=\"img\"\n aria-label={harness}\n data-glyph={harness}\n >\n {brand.map((d) => (\n <path key={d.slice(0, 32)} d={d} />\n ))}\n </svg>\n )\n }\n const Fallback = FALLBACK_GLYPHS[harness] ?? BotGlyph\n const kind = Fallback === BotGlyph ? 'bot' : Fallback === PlugGlyph ? 'plug' : 'terminal'\n return (\n <span role=\"img\" aria-label={harness} data-glyph={kind} className=\"inline-flex\">\n <Fallback className={className} />\n </span>\n )\n}\n","/**\n * `AgentSessionControls` — the CANONICAL model + harness + reasoning-effort\n * cluster a chat composer docks (see \"UI chrome ownership (picker canon)\" in\n * AGENTS.md). One component so every product's two composers (and every\n * product) share the same control surface and harness↔model coherence policy.\n *\n * PICKER CANON. The model menu below IS `/web-react`'s `ModelPicker` and the\n * thinking-budget pill IS `EffortPicker` — the canonical ecosystem pickers.\n * sandbox-ui's `dashboard/ModelPicker` and the model menu inside sandbox-ui's\n * `chat/AgentSessionControls` are legacy (deprecated, frozen, removed at\n * sandbox-ui's next major), and the `/chat-react` `ComposerAgentControls`\n * adapter that rendered sandbox-ui's strip is REMOVED — a surface that still\n * renders the sandbox-ui strip is showing the old design; migrate it\n * (props mapping in `docs/ui-picker-canon.md`).\n *\n * Dependency-free beyond React by design: `/web-react` must not force the\n * optional sandbox-ui peer, so this component — the canonical one — can never\n * require it.\n *\n * Two layouts, additive — the default preserves the prior hand-rolled behavior:\n * - `layout=\"inline\"` (default): model, harness, and effort sit side by side as\n * pills. This is the original arrangement; existing call sites that mounted\n * `ModelPicker` + a harness picker + `EffortPicker` in a row get the same UI.\n * - `layout=\"compact\"`: the model picker stays inline and visible; the agent\n * backend (\"harness\") and reasoning-effort controls — internal jargon a user\n * rarely needs — tuck behind a single gear popover with plain-English copy.\n *\n * Harness ↔ model coherence is identical in both layouts, via the substrate's\n * snap helpers (`@tangle-network/agent-app/harness`): changing the harness snaps\n * an incompatible model to that harness's best catalog option; changing the\n * model switches to the model's native harness. Catalog model ids are canonical\n * (\"provider/model\"), which is exactly what the snap helpers expect — no id\n * translation is needed here.\n *\n * Dependency-free beyond React: inline SVG glyphs, CSS-var / Tailwind tokens the\n * app shell defines. The harness picker is rendered inline so this needs no\n * sandbox-ui dependency.\n */\n\nimport { useId, useMemo, useRef, useState, type ReactNode } from 'react'\nimport {\n snapHarnessToModel,\n snapModelToHarness,\n type Harness,\n} from '../harness'\nimport type { CatalogModel } from '../runtime/model-catalog'\nimport { ModelPicker, EffortPicker, CheckGlyph, OVERLAY_SHADOW, pickerRootClass, PopoverSurface, usePopover } from './controls'\nimport type { EffortLevel } from './controls'\nimport { HarnessGlyph } from './harness-glyphs'\n\n/** Plain-English labels for the harnesses a product is likely to expose. Unknown\n * ids fall back to the raw value so a new backend still renders a usable label. */\nconst HARNESS_LABELS: Partial<Record<Harness, string>> = {\n opencode: 'OpenCode (any model)',\n 'claude-code': 'Claude Code (Anthropic)',\n codex: 'Codex (OpenAI)',\n 'kimi-code': 'Kimi (Moonshot)',\n amp: 'Amp',\n 'factory-droids': 'Factory Droids',\n cursor: 'Cursor',\n hermes: 'Hermes',\n forge: 'Forge',\n pi: 'Pi',\n openclaw: 'OpenClaw',\n acp: 'ACP',\n 'cli-base': 'CLI',\n}\n\nfunction harnessLabel(h: Harness): string {\n return HARNESS_LABELS[h] ?? h\n}\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\n/** lucide `lock` — the closed padlock on a pinned harness trigger. */\nfunction LockGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\" />\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\" />\n </svg>\n )\n}\n\nfunction GearGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z\" />\n </svg>\n )\n}\n\n/** Tailwind utilities for keyboard-visible focus on popover options + triggers. */\nconst FOCUS_RING =\n ''\n\n/**\n * Pill-styled harness picker — inline, no sandbox-ui dependency. The brand\n * marks come from `./harness-glyphs` (the set the legacy sandbox-ui picker\n * shipped, vendored inline).\n *\n * `rounded-full` + `min-h-[36px]`, not `rounded-lg` at whatever height the\n * padding gives: this pill sits beside `ModelPicker` and `EffortPicker` in\n * both layouts, and both of those are 36px pills. A single odd-shaped control\n * is what made the compact popover read as a pile of unrelated widgets rather\n * than one selector stack.\n *\n * `fullWidth` is opt-in and means what it means on `EffortPicker` — see\n * {@link pickerRootClass}.\n *\n * `lockReason` PINS the control: it keeps its selector shape and keeps\n * reporting the harness the thread is on, opens nothing, and explains itself\n * on hover AND on keyboard focus. Three deliberate choices there:\n *\n * - `aria-disabled`, never the native `disabled` attribute. A disabled button\n * is removed from the tab order and fires no pointer events in most\n * browsers, so the one control that has something to explain would become\n * the one control that can never be asked.\n * - the reason rides a permanent visually-hidden node that `aria-describedby`\n * points at, so assistive tech has it whether or not the floating hint is\n * up; the floating copy is `aria-hidden` so nothing is announced twice.\n * - the hint is a {@link PopoverSurface}, not an absolutely-positioned div —\n * the compact panel is an `overflow-y-auto` box, which clips a positioned\n * descendant, and that surface is this package's answer to exactly that.\n */\nfunction HarnessPicker({\n value,\n onChange,\n available,\n fullWidth = false,\n lockReason,\n}: {\n value: Harness\n onChange: (h: Harness) => void\n available?: ReadonlyArray<Harness>\n fullWidth?: boolean\n lockReason?: string\n}) {\n const [open, setOpen] = useState(false)\n const [hintOpen, setHintOpen] = useState(false)\n const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen)\n const hintPanelRef = useRef<HTMLDivElement>(null)\n const panelId = useId()\n const reasonId = useId()\n const locked = lockReason !== undefined\n const options = available ?? (Object.keys(HARNESS_LABELS) as Harness[])\n const showHint = () => setHintOpen(true)\n const hideHint = () => setHintOpen(false)\n return (\n <div ref={containerRef} className={pickerRootClass(fullWidth)}>\n <button\n type=\"button\"\n {...triggerProps}\n aria-haspopup={locked ? undefined : true}\n aria-expanded={locked ? undefined : open}\n aria-controls={!locked && open ? panelId : undefined}\n aria-disabled={locked || undefined}\n aria-describedby={locked ? reasonId : undefined}\n onClick={locked ? undefined : () => setOpen(!open)}\n onMouseEnter={locked ? showHint : undefined}\n onMouseLeave={locked ? hideHint : undefined}\n onFocus={locked ? showHint : undefined}\n onBlur={locked ? hideHint : undefined}\n title=\"Agent backend\"\n className={`inline-flex min-h-[36px] w-full items-center justify-between gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition ${\n locked ? 'cursor-default' : 'hover:bg-accent'\n } ${FOCUS_RING}`}\n >\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <HarnessGlyph harness={value} className=\"h-4 w-4 shrink-0 text-foreground\" />\n <span className=\"truncate\">{harnessLabel(value)}</span>\n </span>\n {locked ? (\n <LockGlyph className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n ) : (\n <ChevronDown className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n )}\n </button>\n {locked && (\n <>\n <span id={reasonId} className=\"sr-only\">\n {lockReason}\n </span>\n <PopoverSurface\n open={hintOpen}\n role=\"tooltip\"\n triggerRef={triggerRef}\n panelRef={hintPanelRef}\n matchTriggerWidth={fullWidth}\n className={`max-w-[248px] rounded-lg border border-card-edge bg-popover px-2.5 py-1.5 text-xs leading-snug text-muted-foreground ${OVERLAY_SHADOW}`}\n >\n <span aria-hidden>{lockReason}</span>\n </PopoverSurface>\n </>\n )}\n <PopoverSurface\n open={!locked && open}\n id={panelId}\n role=\"menu\"\n triggerRef={triggerRef}\n panelRef={panelRef}\n matchTriggerWidth\n className={`max-h-64 min-w-[248px] overflow-y-auto rounded-xl border border-card-edge bg-popover p-1 ${OVERLAY_SHADOW}`}\n >\n {options.map((h) => (\n <button\n key={h}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={h === value}\n onClick={() => {\n onChange(h)\n setOpen(false)\n }}\n className={`flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-left text-sm transition ${FOCUS_RING} ${\n h === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent'\n }`}\n >\n <HarnessGlyph harness={h} className=\"h-4 w-4 shrink-0 text-foreground\" />\n <span className=\"truncate\">{harnessLabel(h)}</span>\n {h === value && <CheckGlyph className=\"ml-auto h-3.5 w-3.5 shrink-0 text-primary\" />}\n </button>\n ))}\n </PopoverSurface>\n </div>\n )\n}\n\nexport interface AgentSessionControlsProps {\n /** Catalog models — canonical provider-prefixed ids. */\n models: CatalogModel[]\n modelsLoading?: boolean\n /** Selected canonical model id. */\n model: string\n onModelChange(modelId: string): void\n /** Current harness; harness↔model coherence is enforced on every change. */\n harness: Harness\n onHarnessChange(harness: Harness): void\n /** Harnesses to offer; defaults to the labeled set. */\n availableHarnesses?: ReadonlyArray<Harness>\n /** Reasoning-effort value + setter. Shown only when the selected model\n * `supportsReasoning`, matching `EffortPicker`'s guidance. */\n effort: string\n onEffortChange(effort: string): void\n /**\n * Levels to offer, forwarded verbatim to {@link EffortPicker}. Omit for the\n * default vocabulary.\n *\n * A product whose backend applies only a SUBSET of the levels for the\n * selected harness/model passes that subset here. Without it the strip\n * offers every level and the backend silently ignores the ones it does not\n * apply — a control that reports a choice the system never made.\n *\n * This is the COMPLETE renderable set, not an allow-list layered over a\n * default one — the removed `ComposerAgentControls`' `available` list was the\n * latter, and its picker injected the `auto` sentinel itself. A list that\n * omits the current {@link effort} is still safe: `EffortPicker` reconciles\n * the selected value into the rendered list under its own name rather than\n * resolving it to a different entry (`reconcileEffortLevels`). Build the list\n * from engine ids with `effortLevelsFromIds`; the migration is in\n * `docs/ui-picker-canon.md`.\n */\n effortLevels?: readonly EffortLevel[]\n /**\n * `inline` (default): model, harness, effort side by side — the prior\n * behavior. `compact`: model inline, harness + effort behind a gear popover.\n */\n layout?: 'inline' | 'compact'\n /** Hide the harness control entirely (single-harness products). */\n showHarness?: boolean\n /**\n * PIN the harness and say why, in the user's words (\"This thread already has\n * messages — start a new chat to switch backend\"). Presence IS the lock:\n * there is no separate boolean, because a lock a user cannot read is the\n * thing this prop exists to replace.\n *\n * The control stays VISIBLE and reports the harness the thread is on — the\n * shape a locked selector has to keep, since a thread whose backend is fixed\n * is exactly when a user wants to know what it is. Hiding it (`showHarness:\n * false`) is what pushed products into rendering their own lock label\n * outside the panel.\n *\n * While locked, `onHarnessChange` is never called — not from the picker, and\n * not from the model↔harness coherence policy either. See\n * {@link useCoherentHandlers}.\n */\n harnessLockReason?: string\n renderProviderBadge?: (provider: string) => ReactNode\n className?: string\n}\n\n/**\n * Apply the harness↔model coherence policy and emit the resulting change(s).\n * Returned from a hook-free helper so both layouts share one implementation.\n *\n * A LOCKED harness ({@link AgentSessionControlsProps.harnessLockReason}) is\n * authoritative over the snap: picking a model whose native backend differs\n * still changes the model, and leaves the harness alone. The alternative —\n * snapping a harness the UI has just told the user cannot change — is the one\n * behaviour a lock must not have.\n */\nfunction useCoherentHandlers(props: AgentSessionControlsProps) {\n const { model, models, harness, onModelChange, onHarnessChange, harnessLockReason } = props\n const canonicalIds = useMemo(() => models.map((m) => m.id), [models])\n const harnessLocked = harnessLockReason !== undefined\n\n const onModel = (next: string) => {\n onModelChange(next)\n if (harnessLocked) return\n const nextHarness = snapHarnessToModel(harness, next)\n if (nextHarness !== harness) onHarnessChange(nextHarness)\n }\n\n const onHarness = (next: Harness) => {\n onHarnessChange(next)\n const snapped = snapModelToHarness(next, model, canonicalIds)\n if (snapped !== model) onModelChange(snapped)\n }\n\n return { onModel, onHarness }\n}\n\nexport function AgentSessionControls(props: AgentSessionControlsProps) {\n const {\n models,\n modelsLoading,\n model,\n harness,\n availableHarnesses,\n effort,\n onEffortChange,\n effortLevels,\n layout = 'inline',\n showHarness = true,\n harnessLockReason,\n renderProviderBadge,\n className,\n } = props\n const { onModel, onHarness } = useCoherentHandlers(props)\n const [open, setOpen] = useState(false)\n const { containerRef: popoverRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen)\n const panelId = useId()\n\n const selectedModel = models.find((m) => m.id === model)\n const showEffort = selectedModel?.supportsReasoning ?? true\n\n const modelPicker = (\n <ModelPicker\n value={model}\n onChange={onModel}\n models={models}\n loading={modelsLoading}\n renderProviderBadge={renderProviderBadge}\n />\n )\n\n if (layout === 'inline') {\n return (\n <div className={`flex items-center gap-1.5 ${className ?? ''}`}>\n {modelPicker}\n {showHarness && (\n <HarnessPicker value={harness} onChange={onHarness} available={availableHarnesses} lockReason={harnessLockReason} />\n )}\n {showEffort && <EffortPicker value={effort} onChange={onEffortChange} levels={effortLevels} />}\n </div>\n )\n }\n\n // compact: model inline; harness + effort behind a gear popover.\n const hasAdvanced = showHarness || showEffort\n return (\n <div className={`flex items-center gap-1.5 ${className ?? ''}`}>\n {modelPicker}\n {hasAdvanced && (\n <div ref={popoverRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n {...triggerProps}\n aria-controls={open ? panelId : undefined}\n onClick={() => setOpen(!open)}\n title=\"Model settings — pick the agent backend and how hard it thinks\"\n className={`flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[state=open]:bg-muted ${FOCUS_RING}`}\n data-state={open ? 'open' : 'closed'}\n >\n <GearGlyph className=\"h-4 w-4\" />\n </button>\n <PopoverSurface\n open={open}\n id={panelId}\n triggerRef={triggerRef}\n panelRef={panelRef}\n className={`w-72 space-y-3 overflow-y-auto rounded-xl border border-card-edge bg-popover p-3 ${OVERLAY_SHADOW}`}\n >\n {showHarness && (\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-foreground\">Agent backend</p>\n <HarnessPicker\n value={harness}\n onChange={onHarness}\n available={availableHarnesses}\n fullWidth\n lockReason={harnessLockReason}\n />\n <p className=\"text-xs leading-snug text-muted-foreground\">\n The engine that runs the agent. Switching it keeps your model choice compatible.\n </p>\n </div>\n )}\n {showEffort && (\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-foreground\">Thinking</p>\n <EffortPicker value={effort} onChange={onEffortChange} levels={effortLevels} label=\"\" fullWidth />\n <p className=\"text-xs leading-snug text-muted-foreground\">\n How hard the agent thinks before answering. Higher is slower but more thorough.\n </p>\n </div>\n )}\n </PopoverSurface>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["enabled","useMemo","useRef","useState","jsx","jsxs","useState","useRef","useMemo"]}
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-react/mention-pill.ts","../src/web-react/class-names.ts","../src/web-react/mention-boundaries.ts"],"sourcesContent":["/**\n * Visual contract for a rendered mention pill. The TipTap extension in\n * `mention-editor.tsx` styles its inline atom node with this, and any other\n * surface that renders a resolved mention (e.g. a sent-message transcript)\n * uses the same constant so the two stay one visual contract instead of\n * silently drifting apart. Kept in its own module (no TipTap import) so\n * importing it never pulls the lazily-loaded editor chunk into a consumer's\n * bundle.\n */\nexport const MENTION_PILL_CLASS = 'rounded-md bg-primary/10 px-1 py-0.5 font-medium text-primary'\n","/**\n * One class-attribute join for this subpath.\n *\n * The pattern it replaces is `` `base ${className ?? ''}` ``, which emits\n * `class=\"base \"` for every caller that passes nothing: a trailing separator in\n * the DOM, in every snapshot, and in every assertion that compares the attribute\n * instead of searching it. Interpolating an absent value is the defect — the\n * fix is to never build the attribute by interpolation.\n */\nexport function joinClasses(...parts: ReadonlyArray<string | false | null | undefined>): string {\n const kept: string[] = []\n for (const part of parts) {\n if (typeof part !== 'string') continue\n const trimmed = part.trim()\n if (trimmed.length > 0) kept.push(trimmed)\n }\n return kept.join(' ')\n}\n","/**\n * The one boundary vocabulary for reading `@<path>` mention tokens out of\n * plain text. Two inverse readings share it — the transcript segmenter\n * (`chat-mentions.ts`) and editor-pill restore (`mention-serialize.ts`) — and\n * they must agree, or a token one accepts the other mangles. Kept OUT of the\n * `/web-react` barrel on purpose: these are internals of the grammar, not\n * public API.\n */\n\n/** A character that could plausibly continue the SAME path/filename past a\n * matched token. Without this lookahead a mention of `@a/b.md` would match\n * inside the unrelated `@a/b.md.bak` and split it mid-filename.\n *\n * Unicode-aware because the wire validator (`validateSandboxMentionPath`)\n * deliberately ALLOWS non-ASCII paths — in-box filenames are arbitrary. An\n * ASCII-only class here would accept input the readers then mangle.\n * `\\p{M}` keeps DECOMPOSED filenames whole: without it, a combining accent\n * (`a` + U+0301) reads as a boundary and splits the name after its base\n * letter. */\nexport const PATH_CONTINUATION_CHAR = /[\\p{L}\\p{M}\\p{N}._\\-/]/u\n\n/** A character that, immediately BEFORE an `@`, means the `@` is part of a\n * longer token (an email local part, a handle) rather than a mention start.\n * Unicode-aware for the same reasons, combining marks included. */\nexport const WORD_CHAR = /[\\p{L}\\p{M}\\p{N}]/u\n\n/** The full character (code point) ENDING just before `index`, or undefined\n * at the start. Indexing `text[index - 1]` hands a lone UTF-16 surrogate to\n * the Unicode-aware classes above, which then misread every astral letter —\n * boundary checks must see whole characters. `index` must be a code-point\n * boundary (both callers derive it from `@` positions and id lengths, which\n * are). */\nexport function charBefore(text: string, index: number): string | undefined {\n if (index <= 0) return undefined\n return Array.from(text.slice(Math.max(0, index - 2), index)).pop()\n}\n\n/** The full character (code point) starting at `index`, or undefined past the\n * end — same surrogate rationale and precondition as {@link charBefore}. */\nexport function charAt(text: string, index: number): string | undefined {\n if (index >= text.length) return undefined\n const codePoint = text.codePointAt(index)\n return codePoint === undefined ? undefined : String.fromCodePoint(codePoint)\n}\n"],"mappings":";AASO,IAAM,qBAAqB;;;ACA3B,SAAS,eAAe,OAAiE;AAC9F,QAAM,OAAiB,CAAC;AACxB,aAAW,QAAQ,OAAO;AACxB,QAAI,OAAO,SAAS,SAAU;AAC9B,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,SAAS,EAAG,MAAK,KAAK,OAAO;AAAA,EAC3C;AACA,SAAO,KAAK,KAAK,GAAG;AACtB;;;ACEO,IAAM,yBAAyB;AAK/B,IAAM,YAAY;AAQlB,SAAS,WAAW,MAAc,OAAmC;AAC1E,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO,MAAM,KAAK,KAAK,MAAM,KAAK,IAAI,GAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,EAAE,IAAI;AACnE;AAIO,SAAS,OAAO,MAAc,OAAmC;AACtE,MAAI,SAAS,KAAK,OAAQ,QAAO;AACjC,QAAM,YAAY,KAAK,YAAY,KAAK;AACxC,SAAO,cAAc,SAAY,SAAY,OAAO,cAAc,SAAS;AAC7E;","names":[]}
|