@tangle-network/agent-app 0.46.5 → 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/{chunk-LEGZX5MQ.js → chunk-OOE2TPY2.js} +6 -2
- package/dist/chunk-OOE2TPY2.js.map +1 -0
- 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/package.json +1 -1
- package/dist/chunk-LEGZX5MQ.js.map +0 -1
|
@@ -44,7 +44,10 @@ function preferredModelId(type, catalog) {
|
|
|
44
44
|
if (!catalog) return void 0;
|
|
45
45
|
const models = catalog.models[type] ?? [];
|
|
46
46
|
const preferred = catalog.defaults[type];
|
|
47
|
-
return models.find((model) => model.id === preferred)?.id ?? models.find((model) => model.status !== "unavailable")?.id ?? models[0]?.id;
|
|
47
|
+
return models.find((model) => model.id === preferred && model.status !== "unavailable")?.id ?? models.find((model) => model.status !== "unavailable")?.id ?? models[0]?.id;
|
|
48
|
+
}
|
|
49
|
+
function laneUnavailable(models) {
|
|
50
|
+
return models.length === 0 || models.every((model) => model.status === "unavailable");
|
|
48
51
|
}
|
|
49
52
|
function modelMessage(model, loading, count) {
|
|
50
53
|
if (loading) return "Loading media models...";
|
|
@@ -544,6 +547,7 @@ export {
|
|
|
544
547
|
generationVaultPath,
|
|
545
548
|
selectedModelsWithDefaults,
|
|
546
549
|
preferredModelId,
|
|
550
|
+
laneUnavailable,
|
|
547
551
|
modelMessage,
|
|
548
552
|
buildGenerationRequestBody,
|
|
549
553
|
generationStatus,
|
|
@@ -584,4 +588,4 @@ export {
|
|
|
584
588
|
hashSeed,
|
|
585
589
|
previewWaveformBars
|
|
586
590
|
};
|
|
587
|
-
//# sourceMappingURL=chunk-
|
|
591
|
+
//# sourceMappingURL=chunk-OOE2TPY2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/studio/generation.ts","../src/studio/model-options.ts","../src/studio/ports.ts","../src/studio/audio-preview.ts"],"sourcesContent":["import type { ModelOptionsMetadata } from './model-options'\n\n/** Define generation categories for media including image, video, speech, avatar, and transcription */\nexport type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription'\n\n/** Define possible states representing the progress of a generation process */\nexport type GenerationStatus = 'pending' | 'running' | 'succeeded' | 'failed'\n\n/** Define possible status values for a media model's availability and accessibility */\nexport type MediaModelStatus = 'available' | 'limited' | 'unavailable'\n\n/** Define the structure for a generation entity including its metadata and creation details */\nexport interface Generation {\n id: string\n type: string\n prompt: string\n result: string | null\n model: string | null\n cost: number | null\n createdAt: Date | null\n metadata: Record<string, unknown> | null\n}\n\n/** Describe a catalog media model and its optional wire-level option metadata. */\nexport interface MediaModelOption {\n id: string\n name: string\n provider?: string\n type: GenerationType\n status: MediaModelStatus\n reason?: string\n options?: ModelOptionsMetadata\n}\n\n/** Represent media model catalog with default values, model options, and optional error message */\nexport interface MediaModelCatalogResponse {\n defaults: Record<GenerationType, string>\n models: Record<GenerationType, MediaModelOption[]>\n error?: string\n}\n\n// Order drives the library type filter tabs. The composer offers its own\n// subset (`COMPOSER_TYPES` in studio-react) while avatar/transcription are\n// disabled (#451).\n/** Provide an array of supported generation types for media and content processing */\nexport const GENERATION_TYPES: readonly GenerationType[] = ['image', 'video', 'avatar', 'speech', 'transcription']\n\n/** Resolve whether a string value matches a valid GenerationType */\nexport function isGenerationType(value: string): value is GenerationType {\n return (GENERATION_TYPES as readonly string[]).includes(value)\n}\n\n/** Define the minimum number of images required for processing or validation */\nexport const MIN_IMAGE_COUNT = 1\n/** Define the maximum number of images allowed for upload or display */\nexport const MAX_IMAGE_COUNT = 8\n\n/** Resolve a human-readable relative time string from a given date or return an empty string if null */\nexport function relativeTime(date: Date | null): string {\n if (!date) return ''\n const now = Date.now()\n const diff = now - new Date(date).getTime()\n const minutes = Math.floor(diff / 60000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 7) return `${days}d ago`\n return new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })\n}\n\n/** Resolve the output directory path based on the specified generation type */\nexport function outputPathFor(type: GenerationType): string {\n if (type === 'image') return 'generated/images'\n if (type === 'video') return 'generated/videos'\n if (type === 'avatar') return 'generated/avatars'\n if (type === 'speech') return 'generated/audio'\n return 'generated/transcripts'\n}\n\n/** Resolve the vault path string from a Generation object or return null if unavailable */\nexport function generationVaultPath(generation: Generation): string | null {\n const value = generation.metadata?.vaultPath\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\n/** DEPRECATED (orphaned since #449 deleted its consumer) — resolve selected models by applying catalog defaults.\n * @deprecated Orphaned since its consumer (the pre-revamp ComposerHero) was deleted in #449;\n * the composer re-derives the guard over curated models inline. Kept for external consumers;\n * removal is a breaking change. */\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 && model.status !== 'unavailable')?.id\n ?? models.find((model) => model.status !== 'unavailable')?.id\n ?? models[0]?.id\n}\n\n/** True when a model list offers nothing sendable: no models, or every model unavailable. */\nexport function laneUnavailable(models: readonly MediaModelOption[]): boolean {\n return models.length === 0 || models.every((model) => model.status === 'unavailable')\n}\n\n/** DEPRECATED (the composer renders availability in the pill/menu/lane states since #463) — resolve the status message for a media model.\n * @deprecated The composer no longer renders an availability status line (#463) — availability is\n * carried by the model pill, the menu rows, and the lane-down notice. Kept only for external\n * consumers; removal is a breaking change. */\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":[]}
|
|
@@ -46,11 +46,19 @@ export declare function relativeTime(date: Date | null): string;
|
|
|
46
46
|
export declare function outputPathFor(type: GenerationType): string;
|
|
47
47
|
/** Resolve the vault path string from a Generation object or return null if unavailable */
|
|
48
48
|
export declare function generationVaultPath(generation: Generation): string | null;
|
|
49
|
-
/**
|
|
49
|
+
/** DEPRECATED (orphaned since #449 deleted its consumer) — resolve selected models by applying catalog defaults.
|
|
50
|
+
* @deprecated Orphaned since its consumer (the pre-revamp ComposerHero) was deleted in #449;
|
|
51
|
+
* the composer re-derives the guard over curated models inline. Kept for external consumers;
|
|
52
|
+
* removal is a breaking change. */
|
|
50
53
|
export declare function selectedModelsWithDefaults(current: Partial<Record<GenerationType, string>>, catalog: MediaModelCatalogResponse): Partial<Record<GenerationType, string>>;
|
|
51
54
|
/** Resolve the preferred model ID for a given generation type from the media model catalog */
|
|
52
55
|
export declare function preferredModelId(type: GenerationType, catalog: MediaModelCatalogResponse | null): string | undefined;
|
|
53
|
-
/**
|
|
56
|
+
/** True when a model list offers nothing sendable: no models, or every model unavailable. */
|
|
57
|
+
export declare function laneUnavailable(models: readonly MediaModelOption[]): boolean;
|
|
58
|
+
/** DEPRECATED (the composer renders availability in the pill/menu/lane states since #463) — resolve the status message for a media model.
|
|
59
|
+
* @deprecated The composer no longer renders an availability status line (#463) — availability is
|
|
60
|
+
* carried by the model pill, the menu rows, and the lane-down notice. Kept only for external
|
|
61
|
+
* consumers; removal is a breaking change. */
|
|
54
62
|
export declare function modelMessage(model: MediaModelOption | undefined, loading: boolean, count: number): string | null;
|
|
55
63
|
/** Define fields required to configure and request various types of media generation */
|
|
56
64
|
export interface GenerationRequestFields {
|
package/dist/studio/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
imageToVideoSibling,
|
|
27
27
|
isGenerationType,
|
|
28
28
|
isLocalGeneration,
|
|
29
|
+
laneUnavailable,
|
|
29
30
|
latestBatchOf,
|
|
30
31
|
mergeGenerationPages,
|
|
31
32
|
mergeLiveGeneration,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
textToVideoSibling,
|
|
48
49
|
userSafeGenerationMessage,
|
|
49
50
|
validateCustomImageSize
|
|
50
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-OOE2TPY2.js";
|
|
51
52
|
export {
|
|
52
53
|
FALLBACK_VIDEO_MODEL_OPTIONS,
|
|
53
54
|
GENERATION_TYPES,
|
|
@@ -76,6 +77,7 @@ export {
|
|
|
76
77
|
imageToVideoSibling,
|
|
77
78
|
isGenerationType,
|
|
78
79
|
isLocalGeneration,
|
|
80
|
+
laneUnavailable,
|
|
79
81
|
latestBatchOf,
|
|
80
82
|
mergeGenerationPages,
|
|
81
83
|
mergeLiveGeneration,
|
|
@@ -130,13 +130,15 @@ export declare function ReferencePill({ url, onAttach, onRemove, pick, bandRef,
|
|
|
130
130
|
* nothing else, which is the honest reading of "we do not know what this model
|
|
131
131
|
* takes".
|
|
132
132
|
*/
|
|
133
|
-
export declare function ModelPill({ models, value, displayName, provider, onSelect, bandRef, }: {
|
|
133
|
+
export declare function ModelPill({ models, value, displayName, provider, unavailable, onSelect, bandRef, }: {
|
|
134
134
|
models: readonly MediaModelOption[];
|
|
135
135
|
value: string;
|
|
136
136
|
/** What the pill reads. Falls back to the id — including for a model the
|
|
137
137
|
* catalog does not list, such as an image-to-video sibling. */
|
|
138
138
|
displayName: string;
|
|
139
139
|
provider?: string;
|
|
140
|
+
/** The selected model is listed but not routable — the pill carries the warning instead of any status line. */
|
|
141
|
+
unavailable?: boolean;
|
|
140
142
|
onSelect: (id: string) => void;
|
|
141
143
|
bandRef: RefObject<HTMLDivElement | null>;
|
|
142
144
|
}): JSX.Element;
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
hashSeed,
|
|
17
17
|
imageToVideoSibling,
|
|
18
18
|
isLocalGeneration,
|
|
19
|
+
laneUnavailable,
|
|
19
20
|
latestBatchOf,
|
|
20
21
|
mergeGenerationPages,
|
|
21
22
|
mergeLiveGeneration,
|
|
22
23
|
mergeLoaderAndLive,
|
|
23
|
-
modelMessage,
|
|
24
24
|
normalizeImageCount,
|
|
25
25
|
normalizeVaultPath,
|
|
26
26
|
optimisticGeneration,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
textToVideoSibling,
|
|
35
35
|
userSafeGenerationMessage,
|
|
36
36
|
validateCustomImageSize
|
|
37
|
-
} from "../chunk-
|
|
37
|
+
} from "../chunk-OOE2TPY2.js";
|
|
38
38
|
import {
|
|
39
39
|
useInfiniteScroll
|
|
40
40
|
} from "../chunk-KKBTPZIE.js";
|
|
@@ -108,7 +108,7 @@ function useStudioGenerations(loaderGenerations, options = {}) {
|
|
|
108
108
|
|
|
109
109
|
// src/studio-react/studio-composer.tsx
|
|
110
110
|
import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
|
|
111
|
-
import { AudioLines, ArrowUp, Image as ImageIcon, Video } from "lucide-react";
|
|
111
|
+
import { AudioLines, ArrowUp, Image as ImageIcon, TriangleAlert as TriangleAlert2, Video } from "lucide-react";
|
|
112
112
|
|
|
113
113
|
// src/studio-react/composer-option-controls.tsx
|
|
114
114
|
import {
|
|
@@ -117,7 +117,7 @@ import {
|
|
|
117
117
|
useId,
|
|
118
118
|
useState as useState2
|
|
119
119
|
} from "react";
|
|
120
|
-
import { ImagePlus, Volume2, VolumeX, X } from "lucide-react";
|
|
120
|
+
import { ImagePlus, TriangleAlert, Volume2, VolumeX, X } from "lucide-react";
|
|
121
121
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
122
122
|
var PILL = "inline-flex h-7 flex-none items-center gap-1.5 whitespace-nowrap rounded-full border border-border bg-card px-2.5 text-[12.5px] font-medium text-foreground transition hover:bg-accent";
|
|
123
123
|
var MENU_PANEL = `flex min-w-[184px] flex-col overflow-y-auto rounded-xl border border-border bg-popover p-1 text-popover-foreground ${OVERLAY_SHADOW}`;
|
|
@@ -578,6 +578,7 @@ function ModelPill({
|
|
|
578
578
|
value,
|
|
579
579
|
displayName,
|
|
580
580
|
provider,
|
|
581
|
+
unavailable,
|
|
581
582
|
onSelect,
|
|
582
583
|
bandRef
|
|
583
584
|
}) {
|
|
@@ -593,10 +594,11 @@ function ModelPill({
|
|
|
593
594
|
...triggerProps,
|
|
594
595
|
"aria-controls": open ? panelId : void 0,
|
|
595
596
|
title: "Model",
|
|
596
|
-
"aria-label": `Model: ${displayName}`,
|
|
597
|
+
"aria-label": `Model: ${displayName}${unavailable ? " (unavailable)" : ""}`,
|
|
597
598
|
onClick: () => setOpen(!open),
|
|
598
|
-
className: PILL
|
|
599
|
+
className: `${PILL}${unavailable ? " border-warning/50" : ""}`,
|
|
599
600
|
children: [
|
|
601
|
+
unavailable && /* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "h-3.5 w-3.5 shrink-0 text-warning", strokeWidth: 2 }),
|
|
600
602
|
provider && /* @__PURE__ */ jsx(ProviderLogo, { provider, size: 14 }),
|
|
601
603
|
/* @__PURE__ */ jsx("span", { className: "max-w-[168px] truncate", children: displayName }),
|
|
602
604
|
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })
|
|
@@ -621,16 +623,18 @@ function ModelPill({
|
|
|
621
623
|
type: "button",
|
|
622
624
|
role: "menuitemradio",
|
|
623
625
|
"aria-checked": model.id === value,
|
|
624
|
-
disabled: model.status === "unavailable",
|
|
625
626
|
onClick: () => {
|
|
626
627
|
onSelect(model.id);
|
|
627
628
|
setOpen(false);
|
|
628
629
|
},
|
|
629
|
-
className:
|
|
630
|
+
className: menuRowClass(model.id === value),
|
|
630
631
|
children: [
|
|
631
|
-
model.provider && /* @__PURE__ */ jsx(ProviderLogo, { provider: model.provider, size: 14 }),
|
|
632
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
633
|
-
model.status
|
|
632
|
+
model.provider && /* @__PURE__ */ jsx("span", { className: model.status === "unavailable" ? "opacity-45" : void 0, children: /* @__PURE__ */ jsx(ProviderLogo, { provider: model.provider, size: 14 }) }),
|
|
633
|
+
/* @__PURE__ */ jsx("span", { className: `truncate${model.status === "unavailable" ? " opacity-45" : ""}`, children: model.name || model.id }),
|
|
634
|
+
model.status === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
635
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[11px] text-warning", children: "Unavailable" }),
|
|
636
|
+
/* @__PURE__ */ jsx(TriangleAlert, { "aria-hidden": true, className: "h-3.5 w-3.5 shrink-0 text-warning", strokeWidth: 2 })
|
|
637
|
+
] }) : model.status === "limited" && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[11px] capitalize text-muted-foreground", children: model.status }),
|
|
634
638
|
model.id === value && /* @__PURE__ */ jsx(CheckGlyph, { className: `${model.status === "available" ? "ml-auto " : ""}h-3.5 w-3.5 shrink-0 text-primary` })
|
|
635
639
|
]
|
|
636
640
|
},
|
|
@@ -696,7 +700,7 @@ function visibleParams(type, options) {
|
|
|
696
700
|
}
|
|
697
701
|
function defaultModelId(type, catalog, curated) {
|
|
698
702
|
const preferred = preferredModelId(type, catalog);
|
|
699
|
-
if (preferred && curated.some((model) => model.id === preferred)) return preferred;
|
|
703
|
+
if (preferred && curated.some((model) => model.id === preferred && model.status !== "unavailable")) return preferred;
|
|
700
704
|
return curated.find((model) => model.status !== "unavailable")?.id ?? curated[0]?.id ?? "";
|
|
701
705
|
}
|
|
702
706
|
function StudioComposer({
|
|
@@ -748,9 +752,21 @@ function StudioComposer({
|
|
|
748
752
|
const laneModels = useMemo2(() => catalog?.models[type] ?? [], [catalog, type]);
|
|
749
753
|
const curatedModels = useMemo2(() => curateComposerModels(type, laneModels), [laneModels, type]);
|
|
750
754
|
const retained = selectedModels[type];
|
|
751
|
-
const retainedUsable = retained !== void 0 && (!catalog || Boolean(textToVideoSibling(retained)) || curatedModels.some((model) => model.id === retained
|
|
755
|
+
const retainedUsable = retained !== void 0 && (!catalog || Boolean(textToVideoSibling(retained)) || curatedModels.some((model) => model.id === retained));
|
|
752
756
|
const modelId = retainedUsable ? retained : defaultModelId(type, catalog, curatedModels);
|
|
753
757
|
const modelOption = laneModels.find((model) => model.id === modelId);
|
|
758
|
+
useEffect3(() => {
|
|
759
|
+
setSelectedModels((current) => {
|
|
760
|
+
const retainedId = current[type];
|
|
761
|
+
if (!retainedId || !catalog) return current;
|
|
762
|
+
if (textToVideoSibling(retainedId)) return current;
|
|
763
|
+
const row = (catalog.models[type] ?? []).find((model) => model.id === retainedId);
|
|
764
|
+
if (!row || row.status !== "unavailable") return current;
|
|
765
|
+
const next = { ...current };
|
|
766
|
+
delete next[type];
|
|
767
|
+
return next;
|
|
768
|
+
});
|
|
769
|
+
}, [catalog, type]);
|
|
754
770
|
const options = useMemo2(
|
|
755
771
|
() => modelId ? resolveComposerOptions({
|
|
756
772
|
type,
|
|
@@ -776,6 +792,7 @@ function StudioComposer({
|
|
|
776
792
|
const unlistedSibling = !modelOption && Boolean(textToVideoSibling(modelId));
|
|
777
793
|
const referenceSupported = type === "video" && Boolean(imageToVideoSibling(modelId) ?? textToVideoSibling(modelId));
|
|
778
794
|
const modelReady = (Boolean(modelOption) || unlistedSibling) && modelOption?.status !== "unavailable" && !catalogLoading && !catalogError;
|
|
795
|
+
const laneDown = Boolean(catalog) && !catalogLoading && !catalogError && laneUnavailable(curatedModels);
|
|
779
796
|
const canSubmit = Boolean(workspaceId) && modelReady && Boolean(prompt.trim()) && !isSubmitting;
|
|
780
797
|
function selectModel(id) {
|
|
781
798
|
setSelectedModels((current) => ({ ...current, [type]: id }));
|
|
@@ -804,10 +821,7 @@ function StudioComposer({
|
|
|
804
821
|
if (!workspaceId || submitLockRef.current || isSubmitting) return;
|
|
805
822
|
const promptText = prompt.trim();
|
|
806
823
|
if (!promptText) return;
|
|
807
|
-
if (!modelReady)
|
|
808
|
-
if (!catalogLoading) setError("Select an available model");
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
824
|
+
if (!modelReady) return;
|
|
811
825
|
submitLockRef.current = true;
|
|
812
826
|
setIsSubmitting(true);
|
|
813
827
|
setError(null);
|
|
@@ -879,16 +893,19 @@ function StudioComposer({
|
|
|
879
893
|
setIsSubmitting(false);
|
|
880
894
|
}
|
|
881
895
|
}
|
|
882
|
-
const
|
|
883
|
-
const
|
|
884
|
-
const
|
|
896
|
+
const notice = catalogError ?? error;
|
|
897
|
+
const laneLabel = SEGMENTS.find((segment) => segment.type === type).label;
|
|
898
|
+
const laneDownMessage = curatedModels.length > 0 ? `${laneLabel} models are temporarily unavailable` : `No ${laneLabel.toLowerCase()} models are available`;
|
|
885
899
|
return /* @__PURE__ */ jsxs2(
|
|
886
900
|
"section",
|
|
887
901
|
{
|
|
888
902
|
"data-variant": variant,
|
|
889
903
|
className: `rounded-[14px] border border-border bg-card px-2.5 pb-[9px] pt-2 shadow-sm transition focus-within:border-primary focus-within:ring-[3px] focus-within:ring-ring/30 ${className ?? ""}`,
|
|
890
904
|
children: [
|
|
891
|
-
/* @__PURE__ */
|
|
905
|
+
laneDown ? /* @__PURE__ */ jsxs2("p", { className: "flex min-h-0 items-center gap-2 px-1.5 pb-3 pt-1.5 text-[13px] font-medium text-warning", children: [
|
|
906
|
+
/* @__PURE__ */ jsx2(TriangleAlert2, { "aria-hidden": true, className: "h-4 w-4 shrink-0", strokeWidth: 2 }),
|
|
907
|
+
/* @__PURE__ */ jsx2("span", { children: laneDownMessage })
|
|
908
|
+
] }) : /* @__PURE__ */ jsx2(
|
|
892
909
|
"textarea",
|
|
893
910
|
{
|
|
894
911
|
value: prompt,
|
|
@@ -914,6 +931,7 @@ function StudioComposer({
|
|
|
914
931
|
value: modelId,
|
|
915
932
|
displayName: modelOption?.name || modelId || "Select a model",
|
|
916
933
|
provider: modelOption?.provider,
|
|
934
|
+
unavailable: modelOption?.status === "unavailable",
|
|
917
935
|
onSelect: chooseModel,
|
|
918
936
|
bandRef
|
|
919
937
|
}
|
|
@@ -973,7 +991,7 @@ function StudioComposer({
|
|
|
973
991
|
}
|
|
974
992
|
)
|
|
975
993
|
] }),
|
|
976
|
-
notice && /* @__PURE__ */ jsx2("p", { className:
|
|
994
|
+
notice && /* @__PURE__ */ jsx2("p", { className: "px-1.5 pt-1.5 text-[12px] text-destructive", children: notice })
|
|
977
995
|
]
|
|
978
996
|
}
|
|
979
997
|
);
|