@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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/web-react/class-names.ts
|
|
2
|
+
function joinClasses(...parts) {
|
|
3
|
+
const kept = [];
|
|
4
|
+
for (const part of parts) {
|
|
5
|
+
if (typeof part !== "string") continue;
|
|
6
|
+
const trimmed = part.trim();
|
|
7
|
+
if (trimmed.length > 0) kept.push(trimmed);
|
|
8
|
+
}
|
|
9
|
+
return kept.join(" ");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
joinClasses
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chunk-L7MB2LLM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web-react/class-names.ts"],"sourcesContent":["/**\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"],"mappings":";AASO,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;","names":[]}
|
|
@@ -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":[]}
|
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
PATH_CONTINUATION_CHAR,
|
|
4
4
|
WORD_CHAR,
|
|
5
5
|
charAt,
|
|
6
|
-
charBefore
|
|
6
|
+
charBefore
|
|
7
|
+
} from "./chunk-3HUFJ5LO.js";
|
|
8
|
+
import {
|
|
7
9
|
joinClasses
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-L7MB2LLM.js";
|
|
9
11
|
import {
|
|
10
12
|
PopoverSurface
|
|
11
13
|
} from "./chunk-YDOQE47G.js";
|
|
@@ -518,4 +520,4 @@ export {
|
|
|
518
520
|
loadMentionEditor,
|
|
519
521
|
loadTiptapModules
|
|
520
522
|
};
|
|
521
|
-
//# sourceMappingURL=mention-editor-
|
|
523
|
+
//# sourceMappingURL=mention-editor-WW7UVZMR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web-react/mention-editor.tsx","../src/web-react/mention-list.tsx","../src/web-react/mention-serialize.ts"],"sourcesContent":["/**\n * The mention-capable rich input behind `ChatComposer`'s `mention` prop.\n *\n * The five `@tiptap/*` packages are OPTIONAL peers, and every value access to\n * them goes through `loadTiptapModules()`'s dynamic `import()` — never a\n * static named import. A bundler resolves a missing optional peer to a stub\n * module with a default export only, so a static `import { mergeAttributes }`\n * fails the CONSUMER'S build even when nothing ever renders the mention path\n * (measured against Vite 7's `__vite-optional-peer-dep` stub). The dynamic\n * form builds clean and defers the failure to the first actual editor load,\n * where it throws with the missing packages named — the same doctrine as\n * `sequences-react`'s transcription peer. Type-only imports are erased and\n * stay allowed.\n */\n\nimport type { MentionOptions } from '@tiptap/extension-mention'\nimport type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'\nimport type * as TiptapCore from '@tiptap/core'\nimport type * as TiptapExtensionMention from '@tiptap/extension-mention'\nimport type * as TiptapReact from '@tiptap/react'\nimport type * as TiptapStarterKit from '@tiptap/starter-kit'\nimport type * as TiptapSuggestion from '@tiptap/suggestion'\nimport { useEffect, useId, useRef, useState, type ComponentType } from 'react'\n\nimport { PopoverSurface } from './controls'\nimport { MENTION_PILL_CLASS } from './mention-pill'\nimport { MentionList, type MentionListHandle } from './mention-list'\nimport {\n collectMentions,\n parseMentionValue,\n serializeMentionDoc,\n type MentionDocNode,\n} from './mention-serialize'\nimport type { ComposerMentionProp, MentionItem } from './use-file-mentions'\n\n/** The loaded namespaces of the five optional peers, as one bag. */\nexport interface TiptapModules {\n core: typeof TiptapCore\n extensionMention: typeof TiptapExtensionMention\n react: typeof TiptapReact\n starterKit: typeof TiptapStarterKit\n suggestion: typeof TiptapSuggestion\n}\n\nconst PEERS_MISSING_MESSAGE =\n 'ChatComposer `mention` needs the @tiptap/* optional peers — install @tiptap/core, @tiptap/extension-mention, @tiptap/pm, @tiptap/react, @tiptap/starter-kit and @tiptap/suggestion (>=3.28.0 <4.0.0; @tiptap/suggestion also peers on @floating-ui/dom)'\n\n/** The rejection shapes bundlers produce for an UNINSTALLED module. Only\n * these get enriched with the install-set message — a transient chunk-fetch\n * failure must surface its own cause, or Retry sits next to a misleading\n * \"install the peers\" diagnosis. An unmatched resolution error still fails\n * loud with its raw message, so the classifier can only under-enrich. */\nconst RESOLUTION_FAILURE = /could not resolve|cannot find module|failed to resolve/i\n\n/** Resolve the optional peers, or throw naming every one of them. */\nexport async function loadTiptapModules(): Promise<TiptapModules> {\n let modules: TiptapModules\n try {\n const [core, extensionMention, react, starterKit, suggestion] = await Promise.all([\n import('@tiptap/core'),\n import('@tiptap/extension-mention'),\n import('@tiptap/react'),\n import('@tiptap/starter-kit'),\n import('@tiptap/suggestion'),\n ])\n modules = { core, extensionMention, react, starterKit, suggestion }\n } catch (error) {\n if (error instanceof Error && RESOLUTION_FAILURE.test(error.message)) {\n throw new Error(PEERS_MISSING_MESSAGE, { cause: error })\n }\n throw error\n }\n // A CommonJS-in-production bundler stubs a missing optional peer as a\n // SILENT empty namespace rather than a throwing module. Validate the\n // members the editor actually calls, so that shape still fails loud with\n // the peers named instead of as an undefined-property crash mid-render.\n if (\n typeof modules.core.mergeAttributes !== 'function' ||\n typeof modules.react.useEditor !== 'function' ||\n modules.react.EditorContent === undefined ||\n modules.extensionMention.default === undefined ||\n modules.starterKit.default === undefined ||\n typeof modules.suggestion.exitSuggestion !== 'function'\n ) {\n throw new Error(PEERS_MISSING_MESSAGE)\n }\n return modules\n}\n\nexport interface MentionEditorProps {\n value: string\n onChange: (value: string) => void\n /** Fired when Enter (no Shift, popover closed, not composing) should send. */\n onSubmit: () => void\n placeholder: string\n disabled?: boolean\n autoFocus?: boolean\n /** Pixel floor for the input box. The composer computes it from `minRows`\n * against its own line metrics, so the two input modes cannot drift. */\n minHeight: number\n /** Pixel height the input grows to before it scrolls. */\n maxHeight: number\n mention: ComposerMentionProp\n /** Registers a focus callback the composer wires to Cmd/Ctrl+L; called with\n * `null` on unmount so the composer never focuses a destroyed editor. */\n registerFocus?: (focus: (() => void) | null) => void\n /**\n * Clipboard files pulled off a paste. Returns true when consumed so the\n * editor suppresses its default text paste — the same funnel the textarea\n * path routes through to `onAttach`.\n */\n onPasteFiles?: (files: FileList) => boolean\n}\n\n/** Only text, hard breaks, and atomic mention pills — no marks, no formatting. */\nexport function buildComposerStarterKit(tiptap: TiptapModules) {\n return tiptap.starterKit.default.configure({\n blockquote: false,\n bold: false,\n bulletList: false,\n code: false,\n codeBlock: false,\n dropcursor: false,\n heading: false,\n horizontalRule: false,\n italic: false,\n link: false,\n listItem: false,\n listKeymap: false,\n orderedList: false,\n strike: false,\n trailingNode: false,\n underline: false,\n })\n}\n\n/**\n * The mention node: an inline atom (`inline: true, selectable: false,\n * atom: true` from `@tiptap/extension-mention`) carrying `{ id, label, kind }`,\n * rendered as a themed pill showing the label with the full id in `title`. Atom\n * means the cursor can't enter it and a single backspace removes the whole\n * thing. The `suggestion` config is supplied by the caller so the popover's\n * fetch/keyboard wiring stays in the component.\n */\nexport function buildMentionExtension(\n tiptap: TiptapModules,\n trigger: string,\n suggestion: MentionOptions['suggestion'],\n) {\n const { mergeAttributes } = tiptap.core\n return tiptap.extensionMention.default\n .extend({\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-id'),\n renderHTML: (attrs) => (attrs.id ? { 'data-id': attrs.id } : {}),\n },\n label: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-label'),\n renderHTML: (attrs) => (attrs.label ? { 'data-label': attrs.label } : {}),\n },\n kind: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-kind'),\n renderHTML: (attrs) => (attrs.kind ? { 'data-kind': attrs.kind } : {}),\n },\n }\n },\n })\n .configure({\n HTMLAttributes: { class: MENTION_PILL_CLASS },\n renderText: ({ node }) => `${trigger}${node.attrs.id}`,\n renderHTML: ({ options, node }) => [\n 'span',\n mergeAttributes(options.HTMLAttributes, {\n title: node.attrs.id ?? undefined,\n }),\n `${trigger}${node.attrs.label ?? node.attrs.id}`,\n ],\n suggestion,\n })\n}\n\n/** Matches the composer textarea's own text treatment so the two input modes\n * render identically (`text-base leading-6`, the same padding). `outline-none`\n * is safe for the same reason as on that textarea: the composer card this\n * editor mounts into draws the keyboard indicator through `focus-within:`,\n * so a second outline inside the card would double the ring. */\nconst EDITOR_CLASS =\n 'w-full whitespace-pre-wrap break-words bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none'\n\n/** Trailing debounce on the suggestion fetch so a fast typist doesn't hit the\n * provider once per keystroke. Fixed rather than a prop — 100ms is well under\n * perceived latency and the monotonic `requestId` already makes any race\n * harmless, so there's no tuning knob worth exposing. */\nconst FETCH_DEBOUNCE_MS = 100\n\n/** Stable key for a mention set — order- and id-sensitive, ignores label/kind\n * so cosmetic re-fetches of the same id don't read as a change. JSON-encoded:\n * ids are workspace paths that may contain spaces, so a plain join would make\n * `[\"a b\",\"c\"]` and `[\"a\",\"b c\"]` collide and suppress a real change. */\nfunction mentionsKey(mentions: MentionItem[]): string {\n return JSON.stringify(mentions.map((item) => item.id))\n}\n\n/** `React.lazy` payload: resolve the peers, then build the component. */\nexport async function loadMentionEditor(): Promise<{\n default: ComponentType<MentionEditorProps>\n}> {\n return { default: createMentionEditor(await loadTiptapModules()) }\n}\n\n/**\n * Builds the mention-capable rich input against loaded tiptap modules. Loaded\n * lazily by `ChatComposer` so textarea-only consumers never pull TipTap into\n * their initial bundle. It preserves every textarea behavior — controlled\n * plain-text `value`, Enter to send / Shift+Enter for a newline, placeholder,\n * disabled, autofocus, file-paste routing — and adds `@`-mention pills backed\n * by an async provider.\n *\n * The suggestion list renders through `PopoverSurface` anchored to the input\n * box, like the composer's slash menu: an in-place panel is a panel the host\n * clips away (see the popover canon in AGENTS.md).\n */\nexport function createMentionEditor(tiptap: TiptapModules): ComponentType<MentionEditorProps> {\n const { EditorContent, useEditor } = tiptap.react\n const { exitSuggestion } = tiptap.suggestion\n\n return function MentionEditor({\n value,\n onChange,\n onSubmit,\n placeholder,\n disabled,\n autoFocus,\n minHeight,\n maxHeight,\n mention,\n registerFocus,\n onPasteFiles,\n }: MentionEditorProps) {\n const trigger = mention.trigger ?? '@'\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [items, setItems] = useState<MentionItem[]>([])\n const [loading, setLoading] = useState(false)\n const [errored, setErrored] = useState(false)\n // Mirror of the list's highlight, for `aria-activedescendant` — keyboard\n // focus never leaves the editor, so the highlighted row must be announced\n // through the editable element itself.\n const [activeIndex, setActiveIndex] = useState(0)\n const listboxId = useId()\n\n // The input box PopoverSurface anchors to, and the portaled panel itself.\n const anchorRef = useRef<HTMLDivElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n\n // Live refs keep the once-built editor's callbacks reading current props\n // and state without rebuilding the editor (which would drop cursor +\n // content).\n const openRef = useRef(false)\n const commandRef = useRef<((item: MentionItem) => void) | null>(null)\n const listRef = useRef<MentionListHandle | null>(null)\n // Grows for the component's lifetime — one entry per distinct id the user\n // actually PICKED (or that arrived in the document as a mention node).\n // Deliberately never fed from fetch results: restore-eligibility must\n // track selection, not visibility. Uncapped; fine for a chat composer's\n // lifetime, worth an LRU only if this outlives that.\n const knownRef = useRef<Map<string, MentionItem>>(new Map())\n const requestIdRef = useRef(0)\n // Last mention-id set reported via `onMentionsChange`, so a programmatic\n // restore that lands on the same mentions doesn't re-fire the callback.\n // Seeded with the EMPTY-set key (not ''): the initial document can never\n // hold pills (`knownRef` starts empty), so the first computed key is the\n // empty set's — starting anywhere else fires a spurious `[]` callback.\n const lastMentionsKeyRef = useRef(mentionsKey([]))\n const onSubmitRef = useRef(onSubmit)\n onSubmitRef.current = onSubmit\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const onMentionsChangeRef = useRef(mention.onMentionsChange)\n onMentionsChangeRef.current = mention.onMentionsChange\n const onPasteFilesRef = useRef(onPasteFiles)\n onPasteFilesRef.current = onPasteFiles\n const fetchItemsRef = useRef(mention.fetchItems)\n fetchItemsRef.current = mention.fetchItems\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n editable: !disabled,\n autofocus: autoFocus ? 'end' : false,\n content: parseMentionValue(value, knownRef.current),\n editorProps: {\n attributes: {\n class: EDITOR_CLASS,\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-label': 'Message input',\n 'aria-haspopup': 'listbox',\n },\n handleKeyDown: (_view, event) => {\n if (event.key !== 'Enter' || event.shiftKey) return false\n // Composition (IME) commits via Enter — never send mid-composition.\n if (event.isComposing || event.keyCode === 229) return false\n // Popover open ⇒ Enter belongs to the suggestion plugin (selects).\n if (openRef.current) return false\n event.preventDefault()\n onSubmitRef.current()\n return true\n },\n handlePaste: (_view, event) => {\n const files = event.clipboardData?.files\n if (files && files.length > 0 && onPasteFilesRef.current) {\n return onPasteFilesRef.current(files)\n }\n return false\n },\n },\n onUpdate: ({ editor }) => {\n const json = editor.getJSON() as MentionDocNode\n onChangeRef.current(serializeMentionDoc(json))\n const mentions = collectMentions(json)\n for (const item of mentions) knownRef.current.set(item.id, item)\n // Report only when the mention SET changed — typing prose around an\n // unchanged pill must not re-fire the callback on every keystroke.\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n },\n extensions: [\n buildComposerStarterKit(tiptap),\n buildMentionExtension(tiptap, trigger, {\n char: trigger,\n allowSpaces: false,\n // Items are fetched by this component (so it can model loading and\n // error states), not by the suggestion plugin.\n items: () => [],\n command: ({ editor, range, props }) => {\n const item = props as unknown as MentionItem\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: 'mention',\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n },\n { type: 'text', text: ' ' },\n ])\n .run()\n knownRef.current.set(item.id, item)\n },\n render: () => ({\n onStart: (props: SuggestionProps) => {\n openRef.current = true\n commandRef.current = props.command\n setOpen(true)\n setQuery(props.query)\n },\n onUpdate: (props: SuggestionProps) => {\n commandRef.current = props.command\n setQuery(props.query)\n },\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n exitSuggestion(props.view)\n return true\n }\n return listRef.current?.onKeyDown(props.event) ?? false\n },\n onExit: () => {\n openRef.current = false\n commandRef.current = null\n setOpen(false)\n setItems([])\n setLoading(false)\n setErrored(false)\n },\n }),\n }),\n ],\n },\n [],\n )\n\n // Fetch matches for the active query, trailing-debounced so a fast typist\n // doesn't hit the provider once per keystroke. The monotonic id still\n // drops stale responses (belt-and-suspenders against a provider that\n // resolves out of order); the debounce timer itself is cancelled by the\n // effect cleanup whenever `query` changes again before it fires.\n useEffect(() => {\n if (!open) return\n const requestId = (requestIdRef.current += 1)\n setLoading(true)\n setErrored(false)\n const timer = setTimeout(() => {\n // The provider is called INSIDE the chain so a synchronous throw lands\n // in the same `.catch` as a rejection — outside it, the exception\n // escapes the timer callback and the panel is stuck on \"Searching…\".\n Promise.resolve()\n .then(() => fetchItemsRef.current(query))\n .then((results) => {\n if (requestId !== requestIdRef.current) return\n // Results are NOT written into `knownRef`: merely appearing in a\n // suggestion list must not make an id restore-eligible, or a\n // later restore would promote prose the user typed (never picked)\n // into a structured mention. Only a pick (`command`) or a mention\n // already in the document teaches the restore path an id.\n setItems(results)\n setLoading(false)\n })\n .catch(() => {\n if (requestId !== requestIdRef.current) return\n setErrored(true)\n setLoading(false)\n })\n }, FETCH_DEBOUNCE_MS)\n return () => clearTimeout(timer)\n }, [open, query])\n\n // Programmatic `value` changes (queued-turn restore, retry refill)\n // re-parse into the document; `@<id>` runs come back as pills only for\n // ids already in `knownRef` at parse time. That conservatism is the\n // contract, not a gap: the serialized form cannot distinguish a PICKED\n // pill from `@text` the user simply typed (a pick is the only thing that\n // creates a mention in-session), so resolving unknown runs against the\n // provider at hydration would fabricate structured attachments from\n // plain prose. An id never fetched or inserted in this session therefore\n // stays literal text, exactly as it would have if typed. Guarded against\n // the onChange→value feedback loop by comparing the serialized form.\n //\n // `setContent` runs with `emitUpdate: false` (this is not a user edit),\n // so `onMentionsChange` — normally wired only off `onUpdate` — is fired\n // here explicitly, but only when the restored mention set actually\n // differs from the last one reported, so a restore that changes plain\n // text around unchanged mentions doesn't re-fire spuriously.\n useEffect(() => {\n if (!editor) return\n if (serializeMentionDoc(editor.getJSON() as MentionDocNode) === value) return\n const doc = parseMentionValue(value, knownRef.current)\n editor.commands.setContent(doc, { emitUpdate: false })\n const mentions = collectMentions(doc)\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n }, [editor, value])\n\n useEffect(() => {\n editor?.setEditable(!disabled)\n }, [editor, disabled])\n\n // Announce the popover through the editable element: focus stays in the\n // editor while ↑/↓ move the listbox highlight, so without\n // `aria-activedescendant` a screen reader hears nothing change. Set\n // imperatively — `editorProps.attributes` is fixed at editor creation.\n useEffect(() => {\n if (!editor) return\n const dom = editor.view.dom\n dom.setAttribute('aria-expanded', open ? 'true' : 'false')\n if (open) dom.setAttribute('aria-controls', listboxId)\n else dom.removeAttribute('aria-controls')\n const hasRows = open && !loading && !errored && items.length > 0\n if (hasRows) dom.setAttribute('aria-activedescendant', `${listboxId}-opt-${activeIndex}`)\n else dom.removeAttribute('aria-activedescendant')\n }, [editor, open, loading, errored, items.length, activeIndex, listboxId])\n\n useEffect(() => {\n if (!editor || !registerFocus) return\n registerFocus(() => editor.commands.focus())\n // Unregister on unmount so the composer's focus shortcut can never call\n // into a destroyed editor.\n return () => registerFocus(null)\n }, [editor, registerFocus])\n\n return (\n <div className=\"relative\">\n <div ref={anchorRef} className=\"overflow-y-auto\" style={{ maxHeight, minHeight }}>\n <EditorContent editor={editor} />\n {value.length === 0 && (\n <div className=\"pointer-events-none absolute left-1.5 top-1 text-base leading-6 text-muted-foreground\">\n {placeholder}\n </div>\n )}\n </div>\n\n {/* The panel's surface classes (and the consumer's `popoverClassName`)\n live on MentionList itself; the surface only places it. The outer\n scroll keeps rows reachable when the viewport cap undercuts the\n list's own max height. */}\n <PopoverSurface\n open={open}\n triggerRef={anchorRef}\n panelRef={panelRef}\n className=\"overflow-y-auto\"\n >\n <MentionList\n ref={listRef}\n id={listboxId}\n items={items}\n loading={loading}\n error={errored}\n emptyText={mention.emptyText}\n renderItem={mention.renderItem}\n onSelect={(item) => commandRef.current?.(item)}\n onActiveChange={setActiveIndex}\n className={mention.popoverClassName}\n />\n </PopoverSurface>\n </div>\n )\n }\n}\n","import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { joinClasses } from './class-names'\nimport type { MentionItem } from './use-file-mentions'\n\n/** Imperative surface the editor's suggestion keymap drives. */\nexport interface MentionListHandle {\n /** Returns true when the key was consumed — the editor must not also act. */\n onKeyDown: (event: KeyboardEvent) => boolean\n}\n\nexport interface MentionListProps {\n items: MentionItem[]\n loading: boolean\n error: boolean\n /** Shown when the fetch resolved to zero items. Default \"No matches\". */\n emptyText?: string\n renderItem?: (item: MentionItem) => ReactNode\n onSelect: (item: MentionItem) => void\n /** Root `role=\"listbox\"` element id; option rows derive stable ids from it\n * (`<id>-opt-<index>`) so the editor can point `aria-activedescendant` at\n * the highlighted row. */\n id?: string\n /** Fired whenever the highlight moves (keys, hover, or a result-set\n * re-home) — what keeps the editor's `aria-activedescendant` current. */\n onActiveChange?: (index: number) => void\n /** Extra classes merged onto the panel's root element, applied last so\n * they win over the component's own. */\n className?: string\n}\n\n/**\n * The mention suggestion list: a flat, keyboard-driven menu with loading,\n * empty, and error states. Selection is owned here so ↑/↓ and Enter/Tab\n * resolve against the highlighted row; every key it handles is reported\n * consumed so the composer's Enter-to-send never fires while open. The panel\n * carries its own surface classes but no positioning — the editor places it\n * through `PopoverSurface`, per the popover canon.\n */\nexport const MentionList = forwardRef<MentionListHandle, MentionListProps>(\n function MentionList(\n { items, loading, error, emptyText = 'No matches', renderItem, onSelect, id, onActiveChange, className },\n ref,\n ) {\n const [selected, setSelected] = useState(0)\n // Mirrors `selected` so the key handler reads the live index even when keys\n // arrive faster than React commits (e.g. synchronous test-driven calls).\n const selectedRef = useRef(0)\n const onActiveChangeRef = useRef(onActiveChange)\n onActiveChangeRef.current = onActiveChange\n // One entry per rendered row, so the highlight can be scrolled into view\n // from an effect (once per highlight move) instead of a ref callback\n // (which re-fires on every re-render of the row).\n const optionRefs = useRef<Array<HTMLButtonElement | null>>([])\n const move = (next: number) => {\n selectedRef.current = next\n setSelected(next)\n onActiveChangeRef.current?.(next)\n }\n\n // Keyboard navigation must keep the highlight visible inside the panel's\n // own scroll (`max-h-64 overflow-y-auto`).\n useEffect(() => {\n optionRefs.current[selected]?.scrollIntoView({ block: 'nearest' })\n }, [selected, items])\n\n // A new result set re-homes the highlight to the top.\n useEffect(() => {\n move(0)\n }, [items])\n\n useImperativeHandle(\n ref,\n () => ({\n onKeyDown(event) {\n // The handler must agree with the render: while loading or errored\n // the rows are not painted, so `items` still holding the PREVIOUS\n // query's results must not make Enter select an invisible file.\n const count = loading || error ? 0 : items.length\n switch (event.key) {\n case 'ArrowDown':\n if (count > 0) move((selectedRef.current + 1) % count)\n return true\n case 'ArrowUp':\n if (count > 0) move((selectedRef.current - 1 + count) % count)\n return true\n case 'Enter':\n // Consume even with nothing to select so the message never\n // submits while the popover is open. Clamp the index: a\n // shrunken result set re-homes the highlight in an effect, and\n // a key can arrive before that effect commits.\n if (count > 0) onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n case 'Tab':\n // Tab selects only when a row exists; with nothing selectable\n // (loading, error, empty) it must fall through to normal focus\n // navigation — consuming it would trap keyboard focus.\n if (count === 0) return false\n onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n default:\n return false\n }\n },\n }),\n [items, loading, error, onSelect],\n )\n\n return (\n <div\n role=\"listbox\"\n id={id}\n aria-label=\"File mentions\"\n className={joinClasses(\n 'max-h-64 min-w-[16rem] max-w-sm overflow-y-auto rounded-xl border border-border',\n 'bg-popover p-1 text-popover-foreground shadow-lg',\n className,\n )}\n >\n {loading && (\n <div className=\"flex items-center gap-2 px-2.5 py-2 text-sm text-muted-foreground\">\n <span className=\"h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary border-t-transparent\" />\n Searching…\n </div>\n )}\n\n {!loading && error && (\n <div className=\"px-2.5 py-2 text-sm text-destructive\">Couldn’t load matches</div>\n )}\n\n {!loading && !error && items.length === 0 && (\n <div className=\"px-2.5 py-2 text-sm text-muted-foreground\">{emptyText}</div>\n )}\n\n {!loading &&\n !error &&\n items.map((item, index) => {\n const active = index === selected\n return (\n <button\n type=\"button\"\n key={item.id}\n id={id ? `${id}-opt-${index}` : undefined}\n role=\"option\"\n aria-selected={active}\n ref={(el) => {\n optionRefs.current[index] = el\n }}\n // Pointer down only PREVENTS the focus steal (a blur would\n // tear down the suggestion mid-select); selection happens on\n // click so mouse, touch, keyboard, and assistive-technology\n // synthesized activations all share the native path — and a\n // secondary-button press never inserts a mention.\n onMouseDown={(event) => {\n event.preventDefault()\n }}\n onClick={() => onSelect(item)}\n // Routes through the same path arrows use so the imperative\n // Enter/Tab handler (which reads `selectedRef`, not `selected`)\n // agrees with the row the pointer is over.\n onMouseEnter={() => move(index)}\n className={joinClasses(\n 'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-sm',\n active ? 'bg-primary/10 text-foreground' : 'text-muted-foreground',\n )}\n >\n {renderItem ? (\n renderItem(item)\n ) : (\n <>\n <span className=\"truncate text-foreground\">{item.label}</span>\n {item.detail && (\n <span className=\"ml-auto truncate text-xs text-muted-foreground\">\n {item.detail}\n </span>\n )}\n </>\n )}\n </button>\n )\n })}\n </div>\n )\n },\n)\n","/**\n * Round-trip between the mention editor's document and the composer's plain\n * `value` string. A mention node serializes to `@<id>`; parsing restores a\n * pill only for ids the session already knows (fetched, inserted, or\n * restored), so unknown `@…` runs stay literal text.\n *\n * The document shape is kept structural (no TipTap import) so this round-trip\n * is unit-testable on its own and never pulls the editor chunk into a bundle.\n */\n\nimport { charAt, charBefore, PATH_CONTINUATION_CHAR, WORD_CHAR } from './mention-boundaries'\nimport type { MentionItem } from './use-file-mentions'\n\n/**\n * The subset of a TipTap/ProseMirror JSON document the mention editor\n * produces: a `doc` of paragraphs, each holding text, hard breaks, and atomic\n * mention nodes.\n */\nexport interface MentionDocNode {\n type: string\n text?: string\n attrs?: { id?: string | null; label?: string | null; kind?: string | null }\n content?: MentionDocNode[]\n}\n\nconst MENTION_TYPE = 'mention'\n\n/**\n * Plain-text serialization of the editor document. Paragraphs join with a\n * newline, hard breaks become a newline, and a mention node becomes `@<id>` —\n * the stable text form the controlled `value` carries.\n */\nexport function serializeMentionDoc(doc: MentionDocNode): string {\n return (doc.content ?? []).map(serializeBlock).join('\\n')\n}\n\nfunction serializeBlock(node: MentionDocNode): string {\n if (node.type === 'paragraph') {\n return (node.content ?? []).map(serializeInline).join('')\n }\n return ''\n}\n\nfunction serializeInline(node: MentionDocNode): string {\n if (node.type === 'text') return node.text ?? ''\n if (node.type === 'hardBreak') return '\\n'\n if (node.type === MENTION_TYPE) return `@${node.attrs?.id ?? ''}`\n return ''\n}\n\n/** Every mention node in the document, in order, as `MentionItem`s. */\nexport function collectMentions(doc: MentionDocNode): MentionItem[] {\n const out: MentionItem[] = []\n const walk = (node: MentionDocNode) => {\n if (node.type === MENTION_TYPE && node.attrs?.id) {\n out.push({\n id: node.attrs.id,\n label: node.attrs.label ?? node.attrs.id,\n kind: node.attrs.kind ?? undefined,\n })\n }\n for (const child of node.content ?? []) walk(child)\n }\n walk(doc)\n return out\n}\n\n/**\n * Parse a controlled `value` string back into an editor document. `@<id>`\n * runs that match a currently-known mention restore as atomic mention nodes;\n * every other `@…` stays literal text. Each line becomes a paragraph — a\n * deliberate asymmetry with `serializeMentionDoc`, which also flattens an\n * in-paragraph `hardBreak` to `\\n`: the string round-trips byte-identically,\n * but a restored Shift+Enter comes back as a paragraph break. The two render\n * identically under the composer's styling (no paragraph margins), so the\n * plain-text `value` contract stays the source of truth.\n */\nexport function parseMentionValue(\n value: string,\n known: Map<string, MentionItem>,\n): MentionDocNode {\n const lines = value.split('\\n')\n return {\n type: 'doc',\n content: lines.map((line) => ({\n type: 'paragraph',\n content: parseLine(line, known),\n })),\n }\n}\n\nfunction parseLine(line: string, known: Map<string, MentionItem>): MentionDocNode[] {\n const content: MentionDocNode[] = []\n let text = ''\n const flush = () => {\n if (text) {\n content.push({ type: 'text', text })\n text = ''\n }\n }\n\n let i = 0\n while (i < line.length) {\n // A word character right before the `@` means it is part of a longer\n // token (an email local part, a handle), never a mention start — the\n // same rule the transcript segmenter applies to persisted messages.\n // Read as a full code point: an astral letter's low surrogate would\n // fail WORD_CHAR and let an embedded `@` start a mention.\n const before = charBefore(line, i)\n const partOfLongerToken = before !== undefined && WORD_CHAR.test(before)\n if (line[i] === '@' && !partOfLongerToken) {\n const id = matchKnownId(line, i + 1, known)\n if (id) {\n const item = known.get(id)!\n flush()\n content.push({\n type: MENTION_TYPE,\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n })\n i += 1 + id.length\n continue\n }\n }\n text += line[i]\n i += 1\n }\n flush()\n return content\n}\n\n/**\n * The longest known id that starts at `pos` and ends on a boundary: end of\n * line, or any full character (never a lone surrogate) that could not\n * continue the same path — the transcript segmenter's rule, so `@a.tsx,`\n * restores its pill while `@a.tsx.bak` never matches the shorter `a.tsx`\n * mid-filename. The scan is linear in `known.size` per `@` run on purpose:\n * `known` holds only the ids PICKED this session (a handful), never the\n * file index, so an index structure would buy nothing.\n */\nfunction matchKnownId(\n line: string,\n pos: number,\n known: Map<string, MentionItem>,\n): string | null {\n let best: string | null = null\n for (const id of known.keys()) {\n if (id.length === 0) continue\n if (best !== null && id.length <= best.length) continue\n if (!line.startsWith(id, pos)) continue\n const next = charAt(line, pos + id.length)\n if (next !== undefined && PATH_CONTINUATION_CHAR.test(next)) continue\n best = id\n }\n return best\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","MentionList","jsx","jsxs","useState","useRef","editor","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/web-react/mention-editor.tsx","../src/web-react/mention-list.tsx","../src/web-react/mention-serialize.ts"],"sourcesContent":["/**\n * The mention-capable rich input behind `ChatComposer`'s `mention` prop.\n *\n * The five `@tiptap/*` packages are OPTIONAL peers, and every value access to\n * them goes through `loadTiptapModules()`'s dynamic `import()` — never a\n * static named import. A bundler resolves a missing optional peer to a stub\n * module with a default export only, so a static `import { mergeAttributes }`\n * fails the CONSUMER'S build even when nothing ever renders the mention path\n * (measured against Vite 7's `__vite-optional-peer-dep` stub). The dynamic\n * form builds clean and defers the failure to the first actual editor load,\n * where it throws with the missing packages named — the same doctrine as\n * `sequences-react`'s transcription peer. Type-only imports are erased and\n * stay allowed.\n */\n\nimport type { MentionOptions } from '@tiptap/extension-mention'\nimport type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'\nimport type * as TiptapCore from '@tiptap/core'\nimport type * as TiptapExtensionMention from '@tiptap/extension-mention'\nimport type * as TiptapReact from '@tiptap/react'\nimport type * as TiptapStarterKit from '@tiptap/starter-kit'\nimport type * as TiptapSuggestion from '@tiptap/suggestion'\nimport { useEffect, useId, useRef, useState, type ComponentType } from 'react'\n\nimport { PopoverSurface } from './controls'\nimport { MENTION_PILL_CLASS } from './mention-pill'\nimport { MentionList, type MentionListHandle } from './mention-list'\nimport {\n collectMentions,\n parseMentionValue,\n serializeMentionDoc,\n type MentionDocNode,\n} from './mention-serialize'\nimport type { ComposerMentionProp, MentionItem } from './use-file-mentions'\n\n/** The loaded namespaces of the five optional peers, as one bag. */\nexport interface TiptapModules {\n core: typeof TiptapCore\n extensionMention: typeof TiptapExtensionMention\n react: typeof TiptapReact\n starterKit: typeof TiptapStarterKit\n suggestion: typeof TiptapSuggestion\n}\n\nconst PEERS_MISSING_MESSAGE =\n 'ChatComposer `mention` needs the @tiptap/* optional peers — install @tiptap/core, @tiptap/extension-mention, @tiptap/pm, @tiptap/react, @tiptap/starter-kit and @tiptap/suggestion (>=3.28.0 <4.0.0; @tiptap/suggestion also peers on @floating-ui/dom)'\n\n/** The rejection shapes bundlers produce for an UNINSTALLED module. Only\n * these get enriched with the install-set message — a transient chunk-fetch\n * failure must surface its own cause, or Retry sits next to a misleading\n * \"install the peers\" diagnosis. An unmatched resolution error still fails\n * loud with its raw message, so the classifier can only under-enrich. */\nconst RESOLUTION_FAILURE = /could not resolve|cannot find module|failed to resolve/i\n\n/** Resolve the optional peers, or throw naming every one of them. */\nexport async function loadTiptapModules(): Promise<TiptapModules> {\n let modules: TiptapModules\n try {\n const [core, extensionMention, react, starterKit, suggestion] = await Promise.all([\n import('@tiptap/core'),\n import('@tiptap/extension-mention'),\n import('@tiptap/react'),\n import('@tiptap/starter-kit'),\n import('@tiptap/suggestion'),\n ])\n modules = { core, extensionMention, react, starterKit, suggestion }\n } catch (error) {\n if (error instanceof Error && RESOLUTION_FAILURE.test(error.message)) {\n throw new Error(PEERS_MISSING_MESSAGE, { cause: error })\n }\n throw error\n }\n // A CommonJS-in-production bundler stubs a missing optional peer as a\n // SILENT empty namespace rather than a throwing module. Validate the\n // members the editor actually calls, so that shape still fails loud with\n // the peers named instead of as an undefined-property crash mid-render.\n if (\n typeof modules.core.mergeAttributes !== 'function' ||\n typeof modules.react.useEditor !== 'function' ||\n modules.react.EditorContent === undefined ||\n modules.extensionMention.default === undefined ||\n modules.starterKit.default === undefined ||\n typeof modules.suggestion.exitSuggestion !== 'function'\n ) {\n throw new Error(PEERS_MISSING_MESSAGE)\n }\n return modules\n}\n\nexport interface MentionEditorProps {\n value: string\n onChange: (value: string) => void\n /** Fired when Enter (no Shift, popover closed, not composing) should send. */\n onSubmit: () => void\n placeholder: string\n disabled?: boolean\n autoFocus?: boolean\n /** Pixel floor for the input box. The composer computes it from `minRows`\n * against its own line metrics, so the two input modes cannot drift. */\n minHeight: number\n /** Pixel height the input grows to before it scrolls. */\n maxHeight: number\n mention: ComposerMentionProp\n /** Registers a focus callback the composer wires to Cmd/Ctrl+L; called with\n * `null` on unmount so the composer never focuses a destroyed editor. */\n registerFocus?: (focus: (() => void) | null) => void\n /**\n * Clipboard files pulled off a paste. Returns true when consumed so the\n * editor suppresses its default text paste — the same funnel the textarea\n * path routes through to `onAttach`.\n */\n onPasteFiles?: (files: FileList) => boolean\n}\n\n/** Only text, hard breaks, and atomic mention pills — no marks, no formatting. */\nexport function buildComposerStarterKit(tiptap: TiptapModules) {\n return tiptap.starterKit.default.configure({\n blockquote: false,\n bold: false,\n bulletList: false,\n code: false,\n codeBlock: false,\n dropcursor: false,\n heading: false,\n horizontalRule: false,\n italic: false,\n link: false,\n listItem: false,\n listKeymap: false,\n orderedList: false,\n strike: false,\n trailingNode: false,\n underline: false,\n })\n}\n\n/**\n * The mention node: an inline atom (`inline: true, selectable: false,\n * atom: true` from `@tiptap/extension-mention`) carrying `{ id, label, kind }`,\n * rendered as a themed pill showing the label with the full id in `title`. Atom\n * means the cursor can't enter it and a single backspace removes the whole\n * thing. The `suggestion` config is supplied by the caller so the popover's\n * fetch/keyboard wiring stays in the component.\n */\nexport function buildMentionExtension(\n tiptap: TiptapModules,\n trigger: string,\n suggestion: MentionOptions['suggestion'],\n) {\n const { mergeAttributes } = tiptap.core\n return tiptap.extensionMention.default\n .extend({\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-id'),\n renderHTML: (attrs) => (attrs.id ? { 'data-id': attrs.id } : {}),\n },\n label: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-label'),\n renderHTML: (attrs) => (attrs.label ? { 'data-label': attrs.label } : {}),\n },\n kind: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-kind'),\n renderHTML: (attrs) => (attrs.kind ? { 'data-kind': attrs.kind } : {}),\n },\n }\n },\n })\n .configure({\n HTMLAttributes: { class: MENTION_PILL_CLASS },\n renderText: ({ node }) => `${trigger}${node.attrs.id}`,\n renderHTML: ({ options, node }) => [\n 'span',\n mergeAttributes(options.HTMLAttributes, {\n title: node.attrs.id ?? undefined,\n }),\n `${trigger}${node.attrs.label ?? node.attrs.id}`,\n ],\n suggestion,\n })\n}\n\n/** Matches the composer textarea's own text treatment so the two input modes\n * render identically (`text-base leading-6`, the same padding). `outline-none`\n * is safe for the same reason as on that textarea: the composer card this\n * editor mounts into draws the keyboard indicator through `focus-within:`,\n * so a second outline inside the card would double the ring. */\nconst EDITOR_CLASS =\n 'w-full whitespace-pre-wrap break-words bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none'\n\n/** Trailing debounce on the suggestion fetch so a fast typist doesn't hit the\n * provider once per keystroke. Fixed rather than a prop — 100ms is well under\n * perceived latency and the monotonic `requestId` already makes any race\n * harmless, so there's no tuning knob worth exposing. */\nconst FETCH_DEBOUNCE_MS = 100\n\n/** Stable key for a mention set — order- and id-sensitive, ignores label/kind\n * so cosmetic re-fetches of the same id don't read as a change. JSON-encoded:\n * ids are workspace paths that may contain spaces, so a plain join would make\n * `[\"a b\",\"c\"]` and `[\"a\",\"b c\"]` collide and suppress a real change. */\nfunction mentionsKey(mentions: MentionItem[]): string {\n return JSON.stringify(mentions.map((item) => item.id))\n}\n\n/** `React.lazy` payload: resolve the peers, then build the component. */\nexport async function loadMentionEditor(): Promise<{\n default: ComponentType<MentionEditorProps>\n}> {\n return { default: createMentionEditor(await loadTiptapModules()) }\n}\n\n/**\n * Builds the mention-capable rich input against loaded tiptap modules. Loaded\n * lazily by `ChatComposer` so textarea-only consumers never pull TipTap into\n * their initial bundle. It preserves every textarea behavior — controlled\n * plain-text `value`, Enter to send / Shift+Enter for a newline, placeholder,\n * disabled, autofocus, file-paste routing — and adds `@`-mention pills backed\n * by an async provider.\n *\n * The suggestion list renders through `PopoverSurface` anchored to the input\n * box, like the composer's slash menu: an in-place panel is a panel the host\n * clips away (see the popover canon in AGENTS.md).\n */\nexport function createMentionEditor(tiptap: TiptapModules): ComponentType<MentionEditorProps> {\n const { EditorContent, useEditor } = tiptap.react\n const { exitSuggestion } = tiptap.suggestion\n\n return function MentionEditor({\n value,\n onChange,\n onSubmit,\n placeholder,\n disabled,\n autoFocus,\n minHeight,\n maxHeight,\n mention,\n registerFocus,\n onPasteFiles,\n }: MentionEditorProps) {\n const trigger = mention.trigger ?? '@'\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [items, setItems] = useState<MentionItem[]>([])\n const [loading, setLoading] = useState(false)\n const [errored, setErrored] = useState(false)\n // Mirror of the list's highlight, for `aria-activedescendant` — keyboard\n // focus never leaves the editor, so the highlighted row must be announced\n // through the editable element itself.\n const [activeIndex, setActiveIndex] = useState(0)\n const listboxId = useId()\n\n // The input box PopoverSurface anchors to, and the portaled panel itself.\n const anchorRef = useRef<HTMLDivElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n\n // Live refs keep the once-built editor's callbacks reading current props\n // and state without rebuilding the editor (which would drop cursor +\n // content).\n const openRef = useRef(false)\n const commandRef = useRef<((item: MentionItem) => void) | null>(null)\n const listRef = useRef<MentionListHandle | null>(null)\n // Grows for the component's lifetime — one entry per distinct id the user\n // actually PICKED (or that arrived in the document as a mention node).\n // Deliberately never fed from fetch results: restore-eligibility must\n // track selection, not visibility. Uncapped; fine for a chat composer's\n // lifetime, worth an LRU only if this outlives that.\n const knownRef = useRef<Map<string, MentionItem>>(new Map())\n const requestIdRef = useRef(0)\n // Last mention-id set reported via `onMentionsChange`, so a programmatic\n // restore that lands on the same mentions doesn't re-fire the callback.\n // Seeded with the EMPTY-set key (not ''): the initial document can never\n // hold pills (`knownRef` starts empty), so the first computed key is the\n // empty set's — starting anywhere else fires a spurious `[]` callback.\n const lastMentionsKeyRef = useRef(mentionsKey([]))\n const onSubmitRef = useRef(onSubmit)\n onSubmitRef.current = onSubmit\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const onMentionsChangeRef = useRef(mention.onMentionsChange)\n onMentionsChangeRef.current = mention.onMentionsChange\n const onPasteFilesRef = useRef(onPasteFiles)\n onPasteFilesRef.current = onPasteFiles\n const fetchItemsRef = useRef(mention.fetchItems)\n fetchItemsRef.current = mention.fetchItems\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n editable: !disabled,\n autofocus: autoFocus ? 'end' : false,\n content: parseMentionValue(value, knownRef.current),\n editorProps: {\n attributes: {\n class: EDITOR_CLASS,\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-label': 'Message input',\n 'aria-haspopup': 'listbox',\n },\n handleKeyDown: (_view, event) => {\n if (event.key !== 'Enter' || event.shiftKey) return false\n // Composition (IME) commits via Enter — never send mid-composition.\n if (event.isComposing || event.keyCode === 229) return false\n // Popover open ⇒ Enter belongs to the suggestion plugin (selects).\n if (openRef.current) return false\n event.preventDefault()\n onSubmitRef.current()\n return true\n },\n handlePaste: (_view, event) => {\n const files = event.clipboardData?.files\n if (files && files.length > 0 && onPasteFilesRef.current) {\n return onPasteFilesRef.current(files)\n }\n return false\n },\n },\n onUpdate: ({ editor }) => {\n const json = editor.getJSON() as MentionDocNode\n onChangeRef.current(serializeMentionDoc(json))\n const mentions = collectMentions(json)\n for (const item of mentions) knownRef.current.set(item.id, item)\n // Report only when the mention SET changed — typing prose around an\n // unchanged pill must not re-fire the callback on every keystroke.\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n },\n extensions: [\n buildComposerStarterKit(tiptap),\n buildMentionExtension(tiptap, trigger, {\n char: trigger,\n allowSpaces: false,\n // Items are fetched by this component (so it can model loading and\n // error states), not by the suggestion plugin.\n items: () => [],\n command: ({ editor, range, props }) => {\n const item = props as unknown as MentionItem\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: 'mention',\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n },\n { type: 'text', text: ' ' },\n ])\n .run()\n knownRef.current.set(item.id, item)\n },\n render: () => ({\n onStart: (props: SuggestionProps) => {\n openRef.current = true\n commandRef.current = props.command\n setOpen(true)\n setQuery(props.query)\n },\n onUpdate: (props: SuggestionProps) => {\n commandRef.current = props.command\n setQuery(props.query)\n },\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n exitSuggestion(props.view)\n return true\n }\n return listRef.current?.onKeyDown(props.event) ?? false\n },\n onExit: () => {\n openRef.current = false\n commandRef.current = null\n setOpen(false)\n setItems([])\n setLoading(false)\n setErrored(false)\n },\n }),\n }),\n ],\n },\n [],\n )\n\n // Fetch matches for the active query, trailing-debounced so a fast typist\n // doesn't hit the provider once per keystroke. The monotonic id still\n // drops stale responses (belt-and-suspenders against a provider that\n // resolves out of order); the debounce timer itself is cancelled by the\n // effect cleanup whenever `query` changes again before it fires.\n useEffect(() => {\n if (!open) return\n const requestId = (requestIdRef.current += 1)\n setLoading(true)\n setErrored(false)\n const timer = setTimeout(() => {\n // The provider is called INSIDE the chain so a synchronous throw lands\n // in the same `.catch` as a rejection — outside it, the exception\n // escapes the timer callback and the panel is stuck on \"Searching…\".\n Promise.resolve()\n .then(() => fetchItemsRef.current(query))\n .then((results) => {\n if (requestId !== requestIdRef.current) return\n // Results are NOT written into `knownRef`: merely appearing in a\n // suggestion list must not make an id restore-eligible, or a\n // later restore would promote prose the user typed (never picked)\n // into a structured mention. Only a pick (`command`) or a mention\n // already in the document teaches the restore path an id.\n setItems(results)\n setLoading(false)\n })\n .catch(() => {\n if (requestId !== requestIdRef.current) return\n setErrored(true)\n setLoading(false)\n })\n }, FETCH_DEBOUNCE_MS)\n return () => clearTimeout(timer)\n }, [open, query])\n\n // Programmatic `value` changes (queued-turn restore, retry refill)\n // re-parse into the document; `@<id>` runs come back as pills only for\n // ids already in `knownRef` at parse time. That conservatism is the\n // contract, not a gap: the serialized form cannot distinguish a PICKED\n // pill from `@text` the user simply typed (a pick is the only thing that\n // creates a mention in-session), so resolving unknown runs against the\n // provider at hydration would fabricate structured attachments from\n // plain prose. An id never fetched or inserted in this session therefore\n // stays literal text, exactly as it would have if typed. Guarded against\n // the onChange→value feedback loop by comparing the serialized form.\n //\n // `setContent` runs with `emitUpdate: false` (this is not a user edit),\n // so `onMentionsChange` — normally wired only off `onUpdate` — is fired\n // here explicitly, but only when the restored mention set actually\n // differs from the last one reported, so a restore that changes plain\n // text around unchanged mentions doesn't re-fire spuriously.\n useEffect(() => {\n if (!editor) return\n if (serializeMentionDoc(editor.getJSON() as MentionDocNode) === value) return\n const doc = parseMentionValue(value, knownRef.current)\n editor.commands.setContent(doc, { emitUpdate: false })\n const mentions = collectMentions(doc)\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n }, [editor, value])\n\n useEffect(() => {\n editor?.setEditable(!disabled)\n }, [editor, disabled])\n\n // Announce the popover through the editable element: focus stays in the\n // editor while ↑/↓ move the listbox highlight, so without\n // `aria-activedescendant` a screen reader hears nothing change. Set\n // imperatively — `editorProps.attributes` is fixed at editor creation.\n useEffect(() => {\n if (!editor) return\n const dom = editor.view.dom\n dom.setAttribute('aria-expanded', open ? 'true' : 'false')\n if (open) dom.setAttribute('aria-controls', listboxId)\n else dom.removeAttribute('aria-controls')\n const hasRows = open && !loading && !errored && items.length > 0\n if (hasRows) dom.setAttribute('aria-activedescendant', `${listboxId}-opt-${activeIndex}`)\n else dom.removeAttribute('aria-activedescendant')\n }, [editor, open, loading, errored, items.length, activeIndex, listboxId])\n\n useEffect(() => {\n if (!editor || !registerFocus) return\n registerFocus(() => editor.commands.focus())\n // Unregister on unmount so the composer's focus shortcut can never call\n // into a destroyed editor.\n return () => registerFocus(null)\n }, [editor, registerFocus])\n\n return (\n <div className=\"relative\">\n <div ref={anchorRef} className=\"overflow-y-auto\" style={{ maxHeight, minHeight }}>\n <EditorContent editor={editor} />\n {value.length === 0 && (\n <div className=\"pointer-events-none absolute left-1.5 top-1 text-base leading-6 text-muted-foreground\">\n {placeholder}\n </div>\n )}\n </div>\n\n {/* The panel's surface classes (and the consumer's `popoverClassName`)\n live on MentionList itself; the surface only places it. The outer\n scroll keeps rows reachable when the viewport cap undercuts the\n list's own max height. */}\n <PopoverSurface\n open={open}\n triggerRef={anchorRef}\n panelRef={panelRef}\n className=\"overflow-y-auto\"\n >\n <MentionList\n ref={listRef}\n id={listboxId}\n items={items}\n loading={loading}\n error={errored}\n emptyText={mention.emptyText}\n renderItem={mention.renderItem}\n onSelect={(item) => commandRef.current?.(item)}\n onActiveChange={setActiveIndex}\n className={mention.popoverClassName}\n />\n </PopoverSurface>\n </div>\n )\n }\n}\n","import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { joinClasses } from './class-names'\nimport type { MentionItem } from './use-file-mentions'\n\n/** Imperative surface the editor's suggestion keymap drives. */\nexport interface MentionListHandle {\n /** Returns true when the key was consumed — the editor must not also act. */\n onKeyDown: (event: KeyboardEvent) => boolean\n}\n\nexport interface MentionListProps {\n items: MentionItem[]\n loading: boolean\n error: boolean\n /** Shown when the fetch resolved to zero items. Default \"No matches\". */\n emptyText?: string\n renderItem?: (item: MentionItem) => ReactNode\n onSelect: (item: MentionItem) => void\n /** Root `role=\"listbox\"` element id; option rows derive stable ids from it\n * (`<id>-opt-<index>`) so the editor can point `aria-activedescendant` at\n * the highlighted row. */\n id?: string\n /** Fired whenever the highlight moves (keys, hover, or a result-set\n * re-home) — what keeps the editor's `aria-activedescendant` current. */\n onActiveChange?: (index: number) => void\n /** Extra classes merged onto the panel's root element, applied last so\n * they win over the component's own. */\n className?: string\n}\n\n/**\n * The mention suggestion list: a flat, keyboard-driven menu with loading,\n * empty, and error states. Selection is owned here so ↑/↓ and Enter/Tab\n * resolve against the highlighted row; every key it handles is reported\n * consumed so the composer's Enter-to-send never fires while open. The panel\n * carries its own surface classes but no positioning — the editor places it\n * through `PopoverSurface`, per the popover canon.\n */\nexport const MentionList = forwardRef<MentionListHandle, MentionListProps>(\n function MentionList(\n { items, loading, error, emptyText = 'No matches', renderItem, onSelect, id, onActiveChange, className },\n ref,\n ) {\n const [selected, setSelected] = useState(0)\n // Mirrors `selected` so the key handler reads the live index even when keys\n // arrive faster than React commits (e.g. synchronous test-driven calls).\n const selectedRef = useRef(0)\n const onActiveChangeRef = useRef(onActiveChange)\n onActiveChangeRef.current = onActiveChange\n // One entry per rendered row, so the highlight can be scrolled into view\n // from an effect (once per highlight move) instead of a ref callback\n // (which re-fires on every re-render of the row).\n const optionRefs = useRef<Array<HTMLButtonElement | null>>([])\n const move = (next: number) => {\n selectedRef.current = next\n setSelected(next)\n onActiveChangeRef.current?.(next)\n }\n\n // Keyboard navigation must keep the highlight visible inside the panel's\n // own scroll (`max-h-64 overflow-y-auto`).\n useEffect(() => {\n optionRefs.current[selected]?.scrollIntoView({ block: 'nearest' })\n }, [selected, items])\n\n // A new result set re-homes the highlight to the top.\n useEffect(() => {\n move(0)\n }, [items])\n\n useImperativeHandle(\n ref,\n () => ({\n onKeyDown(event) {\n // The handler must agree with the render: while loading or errored\n // the rows are not painted, so `items` still holding the PREVIOUS\n // query's results must not make Enter select an invisible file.\n const count = loading || error ? 0 : items.length\n switch (event.key) {\n case 'ArrowDown':\n if (count > 0) move((selectedRef.current + 1) % count)\n return true\n case 'ArrowUp':\n if (count > 0) move((selectedRef.current - 1 + count) % count)\n return true\n case 'Enter':\n // Consume even with nothing to select so the message never\n // submits while the popover is open. Clamp the index: a\n // shrunken result set re-homes the highlight in an effect, and\n // a key can arrive before that effect commits.\n if (count > 0) onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n case 'Tab':\n // Tab selects only when a row exists; with nothing selectable\n // (loading, error, empty) it must fall through to normal focus\n // navigation — consuming it would trap keyboard focus.\n if (count === 0) return false\n onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n default:\n return false\n }\n },\n }),\n [items, loading, error, onSelect],\n )\n\n return (\n <div\n role=\"listbox\"\n id={id}\n aria-label=\"File mentions\"\n className={joinClasses(\n 'max-h-64 min-w-[16rem] max-w-sm overflow-y-auto rounded-xl border border-border',\n 'bg-popover p-1 text-popover-foreground shadow-lg',\n className,\n )}\n >\n {loading && (\n <div className=\"flex items-center gap-2 px-2.5 py-2 text-sm text-muted-foreground\">\n <span className=\"h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary border-t-transparent\" />\n Searching…\n </div>\n )}\n\n {!loading && error && (\n <div className=\"px-2.5 py-2 text-sm text-destructive\">Couldn’t load matches</div>\n )}\n\n {!loading && !error && items.length === 0 && (\n <div className=\"px-2.5 py-2 text-sm text-muted-foreground\">{emptyText}</div>\n )}\n\n {!loading &&\n !error &&\n items.map((item, index) => {\n const active = index === selected\n return (\n <button\n type=\"button\"\n key={item.id}\n id={id ? `${id}-opt-${index}` : undefined}\n role=\"option\"\n aria-selected={active}\n ref={(el) => {\n optionRefs.current[index] = el\n }}\n // Pointer down only PREVENTS the focus steal (a blur would\n // tear down the suggestion mid-select); selection happens on\n // click so mouse, touch, keyboard, and assistive-technology\n // synthesized activations all share the native path — and a\n // secondary-button press never inserts a mention.\n onMouseDown={(event) => {\n event.preventDefault()\n }}\n onClick={() => onSelect(item)}\n // Routes through the same path arrows use so the imperative\n // Enter/Tab handler (which reads `selectedRef`, not `selected`)\n // agrees with the row the pointer is over.\n onMouseEnter={() => move(index)}\n className={joinClasses(\n 'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-sm',\n active ? 'bg-primary/10 text-foreground' : 'text-muted-foreground',\n )}\n >\n {renderItem ? (\n renderItem(item)\n ) : (\n <>\n <span className=\"truncate text-foreground\">{item.label}</span>\n {item.detail && (\n <span className=\"ml-auto truncate text-xs text-muted-foreground\">\n {item.detail}\n </span>\n )}\n </>\n )}\n </button>\n )\n })}\n </div>\n )\n },\n)\n","/**\n * Round-trip between the mention editor's document and the composer's plain\n * `value` string. A mention node serializes to `@<id>`; parsing restores a\n * pill only for ids the session already knows (fetched, inserted, or\n * restored), so unknown `@…` runs stay literal text.\n *\n * The document shape is kept structural (no TipTap import) so this round-trip\n * is unit-testable on its own and never pulls the editor chunk into a bundle.\n */\n\nimport { charAt, charBefore, PATH_CONTINUATION_CHAR, WORD_CHAR } from './mention-boundaries'\nimport type { MentionItem } from './use-file-mentions'\n\n/**\n * The subset of a TipTap/ProseMirror JSON document the mention editor\n * produces: a `doc` of paragraphs, each holding text, hard breaks, and atomic\n * mention nodes.\n */\nexport interface MentionDocNode {\n type: string\n text?: string\n attrs?: { id?: string | null; label?: string | null; kind?: string | null }\n content?: MentionDocNode[]\n}\n\nconst MENTION_TYPE = 'mention'\n\n/**\n * Plain-text serialization of the editor document. Paragraphs join with a\n * newline, hard breaks become a newline, and a mention node becomes `@<id>` —\n * the stable text form the controlled `value` carries.\n */\nexport function serializeMentionDoc(doc: MentionDocNode): string {\n return (doc.content ?? []).map(serializeBlock).join('\\n')\n}\n\nfunction serializeBlock(node: MentionDocNode): string {\n if (node.type === 'paragraph') {\n return (node.content ?? []).map(serializeInline).join('')\n }\n return ''\n}\n\nfunction serializeInline(node: MentionDocNode): string {\n if (node.type === 'text') return node.text ?? ''\n if (node.type === 'hardBreak') return '\\n'\n if (node.type === MENTION_TYPE) return `@${node.attrs?.id ?? ''}`\n return ''\n}\n\n/** Every mention node in the document, in order, as `MentionItem`s. */\nexport function collectMentions(doc: MentionDocNode): MentionItem[] {\n const out: MentionItem[] = []\n const walk = (node: MentionDocNode) => {\n if (node.type === MENTION_TYPE && node.attrs?.id) {\n out.push({\n id: node.attrs.id,\n label: node.attrs.label ?? node.attrs.id,\n kind: node.attrs.kind ?? undefined,\n })\n }\n for (const child of node.content ?? []) walk(child)\n }\n walk(doc)\n return out\n}\n\n/**\n * Parse a controlled `value` string back into an editor document. `@<id>`\n * runs that match a currently-known mention restore as atomic mention nodes;\n * every other `@…` stays literal text. Each line becomes a paragraph — a\n * deliberate asymmetry with `serializeMentionDoc`, which also flattens an\n * in-paragraph `hardBreak` to `\\n`: the string round-trips byte-identically,\n * but a restored Shift+Enter comes back as a paragraph break. The two render\n * identically under the composer's styling (no paragraph margins), so the\n * plain-text `value` contract stays the source of truth.\n */\nexport function parseMentionValue(\n value: string,\n known: Map<string, MentionItem>,\n): MentionDocNode {\n const lines = value.split('\\n')\n return {\n type: 'doc',\n content: lines.map((line) => ({\n type: 'paragraph',\n content: parseLine(line, known),\n })),\n }\n}\n\nfunction parseLine(line: string, known: Map<string, MentionItem>): MentionDocNode[] {\n const content: MentionDocNode[] = []\n let text = ''\n const flush = () => {\n if (text) {\n content.push({ type: 'text', text })\n text = ''\n }\n }\n\n let i = 0\n while (i < line.length) {\n // A word character right before the `@` means it is part of a longer\n // token (an email local part, a handle), never a mention start — the\n // same rule the transcript segmenter applies to persisted messages.\n // Read as a full code point: an astral letter's low surrogate would\n // fail WORD_CHAR and let an embedded `@` start a mention.\n const before = charBefore(line, i)\n const partOfLongerToken = before !== undefined && WORD_CHAR.test(before)\n if (line[i] === '@' && !partOfLongerToken) {\n const id = matchKnownId(line, i + 1, known)\n if (id) {\n const item = known.get(id)!\n flush()\n content.push({\n type: MENTION_TYPE,\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n })\n i += 1 + id.length\n continue\n }\n }\n text += line[i]\n i += 1\n }\n flush()\n return content\n}\n\n/**\n * The longest known id that starts at `pos` and ends on a boundary: end of\n * line, or any full character (never a lone surrogate) that could not\n * continue the same path — the transcript segmenter's rule, so `@a.tsx,`\n * restores its pill while `@a.tsx.bak` never matches the shorter `a.tsx`\n * mid-filename. The scan is linear in `known.size` per `@` run on purpose:\n * `known` holds only the ids PICKED this session (a handful), never the\n * file index, so an index structure would buy nothing.\n */\nfunction matchKnownId(\n line: string,\n pos: number,\n known: Map<string, MentionItem>,\n): string | null {\n let best: string | null = null\n for (const id of known.keys()) {\n if (id.length === 0) continue\n if (best !== null && id.length <= best.length) continue\n if (!line.startsWith(id, pos)) continue\n const next = charAt(line, pos + id.length)\n if (next !== undefined && PATH_CONTINUATION_CHAR.test(next)) continue\n best = id\n }\n return best\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","MentionList","jsx","jsxs","useState","useRef","editor","useEffect"]}
|
|
@@ -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;
|