@tangle-network/agent-app 0.42.5 → 0.42.7
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/{DesignCanvas-AOIVN3SJ.js → DesignCanvas-GUTCY763.js} +2 -2
- package/dist/{DesignCanvasEditor-QQNAZRJQ.js → DesignCanvasEditor-ZO4V7H6A.js} +4 -4
- package/dist/{TimelineEditor-Z7C6BFP5.js → TimelineEditor-SNOAGBV6.js} +2 -2
- package/dist/brand/index.d.ts +35 -0
- package/dist/brand/index.js +89 -0
- package/dist/brand/index.js.map +1 -0
- package/dist/{chunk-YYYXGLLQ.js → chunk-6JYDS34Y.js} +184 -54
- package/dist/chunk-6JYDS34Y.js.map +1 -0
- package/dist/{chunk-FJUOBNIY.js → chunk-A5Z53E3A.js} +3 -3
- package/dist/{chunk-V5XZKHCL.js → chunk-AFQXKJAE.js} +132 -2
- package/dist/chunk-AFQXKJAE.js.map +1 -0
- package/dist/{chunk-S6WS2B32.js → chunk-EX3LD5G6.js} +322 -122
- package/dist/chunk-EX3LD5G6.js.map +1 -0
- package/dist/{chunk-DTS5TZRN.js → chunk-MFI4ZM7W.js} +2 -2
- package/dist/chunk-MFI4ZM7W.js.map +1 -0
- package/dist/{chunk-YEWTVDDT.js → chunk-OVUQNKLM.js} +379 -323
- package/dist/chunk-OVUQNKLM.js.map +1 -0
- package/dist/chunk-ZN5J47UX.js +1 -0
- package/dist/chunk-ZN5J47UX.js.map +1 -0
- package/dist/{command-stack-CkjelfK_.d.ts → command-stack-CcWi_2bN.d.ts} +15 -0
- package/dist/design-canvas-react/engine.d.ts +2 -2
- package/dist/design-canvas-react/engine.js +5 -6
- package/dist/design-canvas-react/index.d.ts +61 -8
- package/dist/design-canvas-react/index.js +10 -9
- package/dist/design-canvas-react/lazy.d.ts +2 -2
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/{lazy-BHHqS8-j.d.ts → lazy-2XBVp1_e.d.ts} +6 -2
- package/dist/sequences-react/index.d.ts +94 -2
- package/dist/sequences-react/index.js +12 -2
- package/dist/sequences-react/index.js.map +1 -1
- package/dist/studio/index.d.ts +1 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio-react/index.js +1 -1
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +1 -1
- package/dist/teams/resend.d.ts +36 -0
- package/dist/teams/resend.js +38 -0
- package/dist/teams/resend.js.map +1 -0
- package/dist/web-react/index.d.ts +57 -6
- package/dist/web-react/index.js +361 -200
- package/dist/web-react/index.js.map +1 -1
- package/package.json +25 -10
- package/dist/chunk-DTS5TZRN.js.map +0 -1
- package/dist/chunk-S6WS2B32.js.map +0 -1
- package/dist/chunk-SQV7NUEM.js +0 -136
- package/dist/chunk-SQV7NUEM.js.map +0 -1
- package/dist/chunk-V5XZKHCL.js.map +0 -1
- package/dist/chunk-YEWTVDDT.js.map +0 -1
- package/dist/chunk-YYYXGLLQ.js.map +0 -1
- /package/dist/{DesignCanvas-AOIVN3SJ.js.map → DesignCanvas-GUTCY763.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-QQNAZRJQ.js.map → DesignCanvasEditor-ZO4V7H6A.js.map} +0 -0
- /package/dist/{TimelineEditor-Z7C6BFP5.js.map → TimelineEditor-SNOAGBV6.js.map} +0 -0
- /package/dist/{chunk-FJUOBNIY.js.map → chunk-A5Z53E3A.js.map} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions, a as DesignCanvasMode } from '../command-stack-
|
|
2
|
-
export { A as ApplySceneResult, b as EditorSceneState, S as SCENE_COMMAND_HISTORY_LIMIT, d as SceneCommand, e as SceneCommandStack, f as SceneCommandStackWithReapply, g as SnapEngine, h as SnapResult, i as SnapTarget, j as SnapTargetKind, k as SnapTargets, Z as ZoomPanMath } from '../command-stack-
|
|
1
|
+
import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions, a as DesignCanvasMode } from '../command-stack-CcWi_2bN.js';
|
|
2
|
+
export { A as ApplySceneResult, b as EditorSceneState, S as SCENE_COMMAND_HISTORY_LIMIT, d as SceneCommand, e as SceneCommandStack, f as SceneCommandStackWithReapply, g as SnapEngine, h as SnapResult, i as SnapTarget, j as SnapTargetKind, k as SnapTargets, Z as ZoomPanMath } from '../command-stack-CcWi_2bN.js';
|
|
3
3
|
import { InsertPageGeometry, InsertTemplate, MultiSetAttrsEntry } from './engine.js';
|
|
4
4
|
export { AddElementInput, AddPageInput, BindSlotInput, DEFAULT_INSERT_TEMPLATES, DUPLICATE_OFFSET, DeleteElementInput, DeletePageInput, DuplicatePageInput, ExportViewSnapshot, GroupElementsInput, MAX_INSERT_DIMENSION, MarqueeSelectOptions, NudgeDelta, ReorderElementInput, ReorderPageInput, ResolvedExportParams, SetAttrsInput, SetDocumentTitleInput, SetPageGuidesInput, SetPagePropsInput, UngroupElementInput, ZoomPanConfig, addElementCommand, addPageCommand, bindSlotCommand, buildInsertImageOp, centeredPosition, collectGridTargets, createSnapEngine, createZoomPanMath, deleteElementCommand, deletePageCommand, documentCropToStageCoords, duplicatePageCommand, fittedSize, groupElementsCommand, hitTestPoint, identifyTaintedSrc, isCrossOriginSrc, isExportHiddenNodeName, marqueeSelect, mintElementId, multiSetAttrsCommand, nudgeDelta, reorderElementCommand, reorderPageCommand, resolveExportParams, setAttrsCommand, setDocumentTitleCommand, setPageGuidesCommand, setPagePropsCommand, ungroupElementCommand } from './engine.js';
|
|
5
5
|
import { c as SceneDocument, g as ScenePage, d as SceneElement, P as PageBleed, a as PageGuides } from '../model-BBwyOuWG.js';
|
|
6
6
|
import { f as ExportPreset } from '../export-presets-DAsU5Wal.js';
|
|
7
7
|
export { d as ExportCropRect, h as bleedAwareExportBounds, s as scaleForPreset } from '../export-presets-DAsU5Wal.js';
|
|
8
|
-
export { D as DesignCanvas, a as DesignCanvasChromeLazy, b as DesignCanvasEditor, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-
|
|
8
|
+
export { D as DesignCanvas, a as DesignCanvasChromeLazy, b as DesignCanvasEditor, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-2XBVp1_e.js';
|
|
9
9
|
import * as react from 'react';
|
|
10
10
|
import { ButtonHTMLAttributes } from 'react';
|
|
11
11
|
import { g as SceneOperation, f as SceneAttrsPatch } from '../operations-DLIvvD00.js';
|
|
@@ -142,8 +142,15 @@ interface WorkspaceViewProps {
|
|
|
142
142
|
onReady?(): void;
|
|
143
143
|
/** Konva render palette. Omitted → light defaults (byte-identical history). */
|
|
144
144
|
render?: CanvasRenderPalette;
|
|
145
|
+
/** Show the branded in-canvas empty state (three doors) while the active page
|
|
146
|
+
* has no elements and the user can write. Default true. Set false to keep a
|
|
147
|
+
* bare blank stage (e.g. when restoring a saved-but-intentionally-empty page). */
|
|
148
|
+
showEmptyState?: boolean;
|
|
149
|
+
/** Focus/open the agent from the empty state's "Ask the agent" door. When
|
|
150
|
+
* omitted that door is hidden — there is no agent surface to send them to. */
|
|
151
|
+
onAskAgent?(): void;
|
|
145
152
|
}
|
|
146
|
-
declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange, className, stack, activePage, onFitRef, onExport, onExportRef, fitOnMount, onReady, render, }: WorkspaceViewProps): react.JSX.Element;
|
|
153
|
+
declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange, className, stack, activePage, onFitRef, onExport, onExportRef, fitOnMount, onReady, render, showEmptyState, onAskAgent, }: WorkspaceViewProps): react.JSX.Element;
|
|
147
154
|
/**
|
|
148
155
|
* Self-contained Konva workspace that creates its own command stack. Mount
|
|
149
156
|
* this when you want the canvas without the toolbar/rulers/pages-strip chrome.
|
|
@@ -153,6 +160,39 @@ declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange,
|
|
|
153
160
|
*/
|
|
154
161
|
declare function Workspace(props: DesignCanvasProps): react.JSX.Element | null;
|
|
155
162
|
|
|
163
|
+
/**
|
|
164
|
+
* In-canvas empty state — the first move on a blank design. Shown by
|
|
165
|
+
* WorkspaceView as a centered overlay while the active page has no user
|
|
166
|
+
* content, so a fresh canvas reads as "ready to start" rather than "broken or
|
|
167
|
+
* empty". Three doors cover the two ways people begin and the agent path:
|
|
168
|
+
* - Start with a template — drop a starter element via the same command stack.
|
|
169
|
+
* - Add an element — insert a text block by hand.
|
|
170
|
+
* - Ask the agent — surfaced only when the host wires `onAskAgent` (an agent
|
|
171
|
+
* panel exists); otherwise the door is omitted rather than rendered dead.
|
|
172
|
+
*
|
|
173
|
+
* Token-styled only (CSS-var tokens + Tailwind semantic names) and stamped with
|
|
174
|
+
* the Tangle mark so the blank state is on-brand. Pointer-events stay scoped to
|
|
175
|
+
* the card: clicking the surrounding canvas still starts a marquee.
|
|
176
|
+
*/
|
|
177
|
+
interface CanvasEmptyStateProps {
|
|
178
|
+
/** Drop a starter template element through the command stack. */
|
|
179
|
+
onStartTemplate(): void;
|
|
180
|
+
/** Insert a single editable element by hand. */
|
|
181
|
+
onAddElement(): void;
|
|
182
|
+
/** Focus the agent panel / open the agent. Omitted → the door is hidden. */
|
|
183
|
+
onAskAgent?(): void;
|
|
184
|
+
/** Heading copy. Overridable; defaults to the cold-open prompt. */
|
|
185
|
+
title?: string;
|
|
186
|
+
/** Supporting line under the heading. Overridable. */
|
|
187
|
+
subtitle?: string;
|
|
188
|
+
className?: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The three-door empty state. Each door is a real action wired to the editor's
|
|
192
|
+
* command stack (or the agent), never a placeholder.
|
|
193
|
+
*/
|
|
194
|
+
declare function CanvasEmptyState({ onStartTemplate, onAddElement, onAskAgent, title, subtitle, className, }: CanvasEmptyStateProps): react.JSX.Element;
|
|
195
|
+
|
|
156
196
|
interface SelectionLayerProps {
|
|
157
197
|
/** Konva stage reference to look up selected nodes by name. */
|
|
158
198
|
stageRef: React.RefObject<Konva.Stage | null>;
|
|
@@ -189,8 +229,12 @@ interface PagesStripProps {
|
|
|
189
229
|
/** Show page-management affordances (add / duplicate / delete). Default true.
|
|
190
230
|
* The review surface passes false: pages are navigated, not authored. */
|
|
191
231
|
canManagePages?: boolean;
|
|
232
|
+
/** Heading shown above the strip so it reads as page management. Overridable;
|
|
233
|
+
* defaults to "Pages". Set to '' to hide the visible label (the container
|
|
234
|
+
* keeps its accessible name regardless). */
|
|
235
|
+
label?: string;
|
|
192
236
|
}
|
|
193
|
-
declare function PagesStrip({ pages, activePageId, canWrite, renderThumbnail, onSelectPage, onAddPage, onDuplicatePage, onDeletePage, onReorderPage, canManagePages, }: PagesStripProps): react.JSX.Element;
|
|
237
|
+
declare function PagesStrip({ pages, activePageId, canWrite, renderThumbnail, onSelectPage, onAddPage, onDuplicatePage, onDeletePage, onReorderPage, canManagePages, label, }: PagesStripProps): react.JSX.Element;
|
|
194
238
|
|
|
195
239
|
interface LayersPanelProps {
|
|
196
240
|
page: ScenePage;
|
|
@@ -244,8 +288,14 @@ interface ToolbarProps {
|
|
|
244
288
|
onUngroup(groupId: string): void;
|
|
245
289
|
onDelete(elementIds: string[]): void;
|
|
246
290
|
onBindSlot(elementId: string, slot: string | null): void;
|
|
291
|
+
/** Field label for the page-size control. Defaults to the clearer "Page size";
|
|
292
|
+
* overridable for consumers that want the older "Preset" wording. */
|
|
293
|
+
pageSizeLabel?: string;
|
|
294
|
+
/** Title for the "turn on print bleed" action. Defaults to "Show print bleed"
|
|
295
|
+
* (the outcome) rather than the print-shop term "bleed". */
|
|
296
|
+
enableBleedLabel?: string;
|
|
247
297
|
}
|
|
248
|
-
declare function Toolbar({ page, selectedElements, canWrite, mode, canUndo, canRedo, gridEnabled, snapEnabled, showRulers, showBleed, onUndo, onRedo, onToggleGrid, onToggleSnap, onToggleRulers, onToggleBleed, onSetAttrs, onSetPageProps, onSetPageGuides, onReorder, onGroup, onUngroup, onDelete, onBindSlot, }: ToolbarProps): react.JSX.Element;
|
|
298
|
+
declare function Toolbar({ page, selectedElements, canWrite, mode, canUndo, canRedo, gridEnabled, snapEnabled, showRulers, showBleed, onUndo, onRedo, onToggleGrid, onToggleSnap, onToggleRulers, onToggleBleed, onSetAttrs, onSetPageProps, onSetPageGuides, onReorder, onGroup, onUngroup, onDelete, onBindSlot, pageSizeLabel, enableBleedLabel, }: ToolbarProps): react.JSX.Element;
|
|
249
299
|
|
|
250
300
|
/**
|
|
251
301
|
* Canvas zoom controls: fit-to-page, 100%, zoom-out/in buttons, and a percent
|
|
@@ -256,8 +306,11 @@ interface ZoomControlsProps {
|
|
|
256
306
|
zoom: number;
|
|
257
307
|
onZoom(zoom: number): void;
|
|
258
308
|
onFit(): void;
|
|
309
|
+
/** Label/aria for the fit action. Defaults to "Fit to screen" — shorter and
|
|
310
|
+
* matching every other tool's wording for the same action. */
|
|
311
|
+
fitLabel?: string;
|
|
259
312
|
}
|
|
260
|
-
declare function ZoomControls({ zoom, onZoom, onFit }: ZoomControlsProps): react.JSX.Element;
|
|
313
|
+
declare function ZoomControls({ zoom, onZoom, onFit, fitLabel }: ZoomControlsProps): react.JSX.Element;
|
|
261
314
|
|
|
262
315
|
/** 28px square (h-7) — used by the main toolbar. */
|
|
263
316
|
declare const BTN = "flex h-7 w-7 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-input)] disabled:cursor-default disabled:opacity-40";
|
|
@@ -514,4 +567,4 @@ declare function buildRulerTicks(input: {
|
|
|
514
567
|
* decimals are rounded to 1 place. Values ≥ 1000 are compacted to "1k" etc. */
|
|
515
568
|
declare function formatRulerLabel(value: number): string;
|
|
516
569
|
|
|
517
|
-
export { BTN, BTN_ACTIVE, BTN_SM, BTN_SM_ACTIVE, type BakedNodeAttrs, CanvasInsertPanel, type CanvasInsertPanelProps, DesignCanvasMode, DesignCanvasProps, ElementNode, type ElementNodeProps, ExportControl, type ExportControlProps, type ExportPageDataUrlOptions, ExportPreset, ExportTriggerOptions, GridLayer, type GridLayerProps, IconButton, type IconButtonProps, type InsertGeneration, InsertPageGeometry, InsertTemplate, LAYERS_PANEL_ROW_LIMIT, type LayerRow, LayersPanel, type LayersPanelProps, MultiSetAttrsEntry, PagesStrip, type PagesStripProps, type RulerTick, Rulers, type RulersProps, SelectionLayer, type SelectionLayerProps, type TickStep, Toolbar, type ToolbarProps, type TransformerNode, Workspace, WorkspaceView, type WorkspaceViewProps, ZoomControls, type ZoomControlsProps, bakeLineTransform, bakeRectTransform, bakeTextTransform, buildRulerTicks, createSceneCommandStack, downloadDataUrl, exportDocumentJson, exportPageDataUrl, flattenLayerTree, formatRulerLabel, selectTickStep };
|
|
570
|
+
export { BTN, BTN_ACTIVE, BTN_SM, BTN_SM_ACTIVE, type BakedNodeAttrs, CanvasEmptyState, type CanvasEmptyStateProps, CanvasInsertPanel, type CanvasInsertPanelProps, DesignCanvasMode, DesignCanvasProps, ElementNode, type ElementNodeProps, ExportControl, type ExportControlProps, type ExportPageDataUrlOptions, ExportPreset, ExportTriggerOptions, GridLayer, type GridLayerProps, IconButton, type IconButtonProps, type InsertGeneration, InsertPageGeometry, InsertTemplate, LAYERS_PANEL_ROW_LIMIT, type LayerRow, LayersPanel, type LayersPanelProps, MultiSetAttrsEntry, PagesStrip, type PagesStripProps, type RulerTick, Rulers, type RulersProps, SelectionLayer, type SelectionLayerProps, type TickStep, Toolbar, type ToolbarProps, type TransformerNode, Workspace, WorkspaceView, type WorkspaceViewProps, ZoomControls, type ZoomControlsProps, bakeLineTransform, bakeRectTransform, bakeTextTransform, buildRulerTicks, createSceneCommandStack, downloadDataUrl, exportDocumentJson, exportPageDataUrl, flattenLayerTree, formatRulerLabel, selectTickStep };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CanvasEmptyState,
|
|
2
3
|
DesignCanvasEditor,
|
|
3
4
|
ElementNode,
|
|
4
5
|
GridLayer,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
downloadDataUrl,
|
|
12
13
|
exportDocumentJson,
|
|
13
14
|
exportPageDataUrl
|
|
14
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-6JYDS34Y.js";
|
|
15
16
|
import {
|
|
16
17
|
BTN,
|
|
17
18
|
BTN_ACTIVE,
|
|
@@ -40,29 +41,28 @@ import {
|
|
|
40
41
|
buildRulerTicks,
|
|
41
42
|
formatRulerLabel,
|
|
42
43
|
selectTickStep
|
|
43
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-OVUQNKLM.js";
|
|
45
|
+
import "../chunk-ZN5J47UX.js";
|
|
44
46
|
import {
|
|
45
47
|
DEFAULT_INSERT_TEMPLATES,
|
|
48
|
+
DUPLICATE_OFFSET,
|
|
46
49
|
MAX_INSERT_DIMENSION,
|
|
47
50
|
buildInsertImageOp,
|
|
48
51
|
centeredPosition,
|
|
49
|
-
fittedSize,
|
|
50
|
-
mintElementId
|
|
51
|
-
} from "../chunk-SQV7NUEM.js";
|
|
52
|
-
import {
|
|
53
|
-
DUPLICATE_OFFSET,
|
|
54
52
|
collectGridTargets,
|
|
55
53
|
createSnapEngine,
|
|
56
54
|
createZoomPanMath,
|
|
57
55
|
documentCropToStageCoords,
|
|
56
|
+
fittedSize,
|
|
58
57
|
hitTestPoint,
|
|
59
58
|
identifyTaintedSrc,
|
|
60
59
|
isCrossOriginSrc,
|
|
61
60
|
isExportHiddenNodeName,
|
|
62
61
|
marqueeSelect,
|
|
62
|
+
mintElementId,
|
|
63
63
|
nudgeDelta,
|
|
64
64
|
resolveExportParams
|
|
65
|
-
} from "../chunk-
|
|
65
|
+
} from "../chunk-AFQXKJAE.js";
|
|
66
66
|
import {
|
|
67
67
|
SCENE_COMMAND_HISTORY_LIMIT,
|
|
68
68
|
addElementCommand,
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
import {
|
|
86
86
|
DesignCanvasChromeLazy,
|
|
87
87
|
DesignCanvasLazy
|
|
88
|
-
} from "../chunk-
|
|
88
|
+
} from "../chunk-A5Z53E3A.js";
|
|
89
89
|
import "../chunk-LVD37K5V.js";
|
|
90
90
|
import {
|
|
91
91
|
assertSceneMediaSrc,
|
|
@@ -529,6 +529,7 @@ export {
|
|
|
529
529
|
BTN_ACTIVE,
|
|
530
530
|
BTN_SM,
|
|
531
531
|
BTN_SM_ACTIVE,
|
|
532
|
+
CanvasEmptyState,
|
|
532
533
|
CanvasInsertPanel,
|
|
533
534
|
DEFAULT_INSERT_TEMPLATES,
|
|
534
535
|
DUPLICATE_OFFSET,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export { a as DesignCanvasChromeLazy, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-
|
|
3
|
-
export { D as DesignCanvasProps } from '../command-stack-
|
|
2
|
+
export { a as DesignCanvasChromeLazy, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-2XBVp1_e.js';
|
|
3
|
+
export { D as DesignCanvasProps } from '../command-stack-CcWi_2bN.js';
|
|
4
4
|
import '../model-BBwyOuWG.js';
|
|
5
5
|
import '../operations-DLIvvD00.js';
|
|
6
6
|
import '../theme/index.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { c as SceneDocument } from './model-BBwyOuWG.js';
|
|
3
|
-
import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions } from './command-stack-
|
|
3
|
+
import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions } from './command-stack-CcWi_2bN.js';
|
|
4
4
|
|
|
5
5
|
/** Callers inject a workspace renderer so this chrome stays Konva-free. The
|
|
6
6
|
* workspace occupies the scrollable area between the rulers and the bottom bar. */
|
|
@@ -45,6 +45,10 @@ interface DesignCanvasFullProps extends DesignCanvasProps {
|
|
|
45
45
|
/** Forwarded from DesignCanvasProps. The Konva render palette; the
|
|
46
46
|
* Konva-free chrome passes it straight through to the workspace. */
|
|
47
47
|
render?: DesignCanvasProps['render'];
|
|
48
|
+
/** Forwarded from DesignCanvasProps. Toggles the branded in-canvas empty
|
|
49
|
+
* state and its agent door. */
|
|
50
|
+
showEmptyState?: DesignCanvasProps['showEmptyState'];
|
|
51
|
+
onAskAgent?: DesignCanvasProps['onAskAgent'];
|
|
48
52
|
onZoomChange(zoom: number): void;
|
|
49
53
|
onPanChange(panX: number, panY: number): void;
|
|
50
54
|
onSelectElements(ids: string[], additive: boolean): void;
|
|
@@ -55,7 +59,7 @@ interface DesignCanvasFullProps extends DesignCanvasProps {
|
|
|
55
59
|
*/
|
|
56
60
|
renderThumbnail(page: SceneDocument['pages'][number]): Promise<string | null>;
|
|
57
61
|
}
|
|
58
|
-
declare function DesignCanvas({ document: initialDocument, rev: initialRev, canWrite, mode, onApplyOperations, onSelectionChange, renderAgentPanel, renderSidePanel, onExport, exportDefaults, className, fitOnMount, onReady, render, renderWorkspace, renderThumbnail, }: DesignCanvasFullProps): react.JSX.Element;
|
|
62
|
+
declare function DesignCanvas({ document: initialDocument, rev: initialRev, canWrite, mode, onApplyOperations, onSelectionChange, renderAgentPanel, renderSidePanel, onExport, exportDefaults, className, fitOnMount, onReady, render, showEmptyState, onAskAgent, pageSizeLabel, enableBleedLabel, fitLabel, renderWorkspace, renderThumbnail, }: DesignCanvasFullProps): react.JSX.Element;
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
65
|
* Mount this component to get the full editor: toolbar, rulers, layers panel,
|
|
@@ -114,6 +114,34 @@ interface TranscriptionProvider {
|
|
|
114
114
|
onProgress?: (fraction: number) => void;
|
|
115
115
|
}): Promise<TranscriptionSegment[]>;
|
|
116
116
|
}
|
|
117
|
+
/** Overridable copy for the editor's product-facing labels. Every field is
|
|
118
|
+
* optional; the editor ships clear defaults (see `DEFAULT_TIMELINE_LABELS`).
|
|
119
|
+
* Accessible names (the `*AriaLabel` fields) stay descriptive for screen
|
|
120
|
+
* readers; the short visible labels carry the on-screen verb. Products rename
|
|
121
|
+
* per their own vocabulary without forking the component. */
|
|
122
|
+
interface TimelineEditorLabels {
|
|
123
|
+
/** Visible verb on the split-at-playhead tool. */
|
|
124
|
+
splitClip?: string;
|
|
125
|
+
splitClipAriaLabel?: string;
|
|
126
|
+
/** Visible verb on the add-caption-at-playhead tool. */
|
|
127
|
+
addCaption?: string;
|
|
128
|
+
addCaptionAriaLabel?: string;
|
|
129
|
+
/** Visible verb on the export action (rendered only when `onCreateExport`). */
|
|
130
|
+
createExport?: string;
|
|
131
|
+
/** Empty-state heading + the three doors. */
|
|
132
|
+
emptyTitle?: string;
|
|
133
|
+
emptyBody?: string;
|
|
134
|
+
emptyTemplateDoor?: string;
|
|
135
|
+
emptyClipDoor?: string;
|
|
136
|
+
emptyAgentDoor?: string;
|
|
137
|
+
/** Phone gate shown instead of the gesture-heavy editor below `sm`. */
|
|
138
|
+
smallScreenTitle?: string;
|
|
139
|
+
smallScreenBody?: string;
|
|
140
|
+
/** Ghost-lane names shown at rest so time reads as a timeline before any
|
|
141
|
+
* track exists. */
|
|
142
|
+
ghostVideoLane?: string;
|
|
143
|
+
ghostCaptionLane?: string;
|
|
144
|
+
}
|
|
117
145
|
interface TimelineEditorProps {
|
|
118
146
|
timeline: SequenceTimeline;
|
|
119
147
|
canWrite: boolean;
|
|
@@ -136,8 +164,24 @@ interface TimelineEditorProps {
|
|
|
136
164
|
renderAssetShelf?(): React.ReactNode;
|
|
137
165
|
/** Frame provider override; omitted → the baseline HTMLVideoElement provider. */
|
|
138
166
|
frameProvider?: VideoFrameProvider;
|
|
167
|
+
/** Overridable product-facing copy; defaults are clear on their own. */
|
|
168
|
+
labels?: TimelineEditorLabels;
|
|
169
|
+
/** Empty-state doors. Supplying a handler renders that door; omitting it
|
|
170
|
+
* hides it. With none supplied the empty state shows guidance only. */
|
|
171
|
+
onStartFromTemplate?(): void;
|
|
172
|
+
onAddClip?(): void;
|
|
173
|
+
onAskAgent?(): void;
|
|
174
|
+
/** Render the export action (a toolbar "Export…" button). Omitted → no export
|
|
175
|
+
* affordance. The handler owns the dialog/flow; this surface only invokes it. */
|
|
176
|
+
onCreateExport?(): void;
|
|
177
|
+
/** Off by default. When true AND the consuming product supplies a branded
|
|
178
|
+
* export pipeline, the export door/button is marked as Tangle-branded. This
|
|
179
|
+
* surface has no render pipeline of its own to brand, so the flag is a
|
|
180
|
+
* forward-compatible hint to `onCreateExport`, never a baked watermark. */
|
|
181
|
+
brandedExport?: boolean;
|
|
139
182
|
className?: string;
|
|
140
183
|
}
|
|
184
|
+
declare const DEFAULT_TIMELINE_LABELS: Required<TimelineEditorLabels>;
|
|
141
185
|
|
|
142
186
|
/**
|
|
143
187
|
* Undo/redo command stack over immutable `EditorTimelineState`.
|
|
@@ -635,6 +679,52 @@ interface TimelinePlayheadProps {
|
|
|
635
679
|
}
|
|
636
680
|
declare function TimelinePlayhead({ frame, zoom }: TimelinePlayheadProps): react.JSX.Element;
|
|
637
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Lazy boundary around the Tangle knot from `../../brand`.
|
|
684
|
+
*
|
|
685
|
+
* `/brand` re-exports the mark from `@tangle-network/sandbox-ui`, an OPT-IN peer
|
|
686
|
+
* (see brand/index.tsx). Static-importing it would pull the peer into every
|
|
687
|
+
* module that renders the timeline — breaking substrate-free environments (and
|
|
688
|
+
* products that haven't installed the peer) at module-eval time. Code-splitting
|
|
689
|
+
* it (the repo's `lazy.tsx` convention) keeps the peer out of the editor's
|
|
690
|
+
* static graph: the knot streams in only when an empty/phone state actually
|
|
691
|
+
* renders, and its absence degrades to empty space rather than a crash.
|
|
692
|
+
*/
|
|
693
|
+
interface BrandMarkProps {
|
|
694
|
+
size: number;
|
|
695
|
+
className?: string;
|
|
696
|
+
}
|
|
697
|
+
declare function BrandMark({ size, className }: BrandMarkProps): react.JSX.Element;
|
|
698
|
+
|
|
699
|
+
interface TimelineEmptyStateProps {
|
|
700
|
+
labels?: TimelineEditorLabels;
|
|
701
|
+
brandedExport?: boolean;
|
|
702
|
+
onStartFromTemplate?(): void;
|
|
703
|
+
onAddClip?(): void;
|
|
704
|
+
onAskAgent?(): void;
|
|
705
|
+
}
|
|
706
|
+
declare function TimelineEmptyState(props: TimelineEmptyStateProps): react.JSX.Element;
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Labeled placeholder lanes drawn behind the empty state so the surface reads
|
|
710
|
+
* as a timeline before any track exists: a "Video" lane and a "Captions" lane
|
|
711
|
+
* with the same sticky header column the real `TimelineTrackRow` uses, sized to
|
|
712
|
+
* the timeline width so the ruler's timecodes line up over real lanes. Purely
|
|
713
|
+
* decorative — no clips, no gestures.
|
|
714
|
+
*/
|
|
715
|
+
interface TimelineGhostLanesProps {
|
|
716
|
+
/** Timeline pixel width (durationFrames * zoom), so ghost lanes match the ruler. */
|
|
717
|
+
laneWidth: number;
|
|
718
|
+
videoLabel: string;
|
|
719
|
+
captionLabel: string;
|
|
720
|
+
}
|
|
721
|
+
declare function TimelineGhostLanes({ laneWidth, videoLabel, captionLabel }: TimelineGhostLanesProps): react.JSX.Element;
|
|
722
|
+
|
|
723
|
+
interface TimelineSmallScreenGateProps {
|
|
724
|
+
labels?: TimelineEditorLabels;
|
|
725
|
+
}
|
|
726
|
+
declare function TimelineSmallScreenGate({ labels }: TimelineSmallScreenGateProps): react.JSX.Element;
|
|
727
|
+
|
|
638
728
|
interface SnapIndicatorLineProps {
|
|
639
729
|
point: SnapPoint | null;
|
|
640
730
|
zoom: number;
|
|
@@ -645,8 +735,10 @@ interface ZoomControlProps {
|
|
|
645
735
|
zoomMath: ZoomMath;
|
|
646
736
|
zoom: number;
|
|
647
737
|
onZoomChange(zoom: number): void;
|
|
738
|
+
/** Fit-to-sequence pixels-per-frame; clicking the readout snaps back to it. */
|
|
739
|
+
fitZoom?: number;
|
|
648
740
|
}
|
|
649
|
-
declare function ZoomControl({ zoomMath, zoom, onZoomChange }: ZoomControlProps): react.JSX.Element;
|
|
741
|
+
declare function ZoomControl({ zoomMath, zoom, onZoomChange, fitZoom }: ZoomControlProps): react.JSX.Element;
|
|
650
742
|
|
|
651
743
|
/**
|
|
652
744
|
* Fuses several `TimelineCommand`s into one undo step so a single gesture
|
|
@@ -763,4 +855,4 @@ declare function chooseMoveSnap(input: {
|
|
|
763
855
|
|
|
764
856
|
declare const SequenceTimelineEditorLazy: react__default.LazyExoticComponent<typeof TimelineEditor>;
|
|
765
857
|
|
|
766
|
-
export { type AddCaptionInput, type ApplySnapOptions, type AudioBufferLike, COMMAND_HISTORY_LIMIT, type ClipIdResolver, type ClipMoveCommit, type ClipTrimCommit, type CommandStack, DEFAULT_MAX_MEDIA_ELEMENTS, DEFAULT_WHISPER_MODEL, type DeleteClipInput, type EditorTimelineState, type FrameRect, type LetterboxRect, type MediaElementPool, type MoveClipInput, type MoveDragInput, type PlaceClipInput, type PlaybackClock, type PlaybackClockConfig, type PooledElementLease, PreviewCanvas, type PreviewCanvasProps, SEEK_TIMEOUT_MS, SEEK_TOLERANCE_SECONDS, SEQUENCE_MEDIA_DRAG_TYPE, SequenceTimelineEditorLazy, type SetClipTextInput, SnapIndicatorLine, type SnapIndicatorLineProps, type SnapPoint, type SnapResult, type SplitClipInput, TimelineClipChip, type TimelineClipChipProps, type TimelineCommand, TimelineEditor, type TimelineEditorProps, TimelinePlayhead, type TimelinePlayheadProps, TimelineRuler, type TimelineRulerProps, type TimelineSnapPoint, TimelineTrackRow, type TimelineTrackRowProps, type ToggleClipDisabledInput, type TranscriptionProvider, type TranscriptionSegment, type TrimClipInput, type TrimEndDragInput, type TrimStartDragInput, type TrimStartDragResult, type VideoFrameProvider, type ViewportTransform, type WaveformData, type WhisperOutput, ZoomControl, type ZoomControlProps, type ZoomMath, type ZoomMathConfig, addCaptionCommand, applySnap, captionFontPx, chooseMoveSnap, classifyMediaUrl, clipChipGeometry, collectSnapPoints, compositeCommand, computeWaveform, containFitRect, createCommandStack, createImageFrameProvider, createMediaElementPool, createPlaybackClock, createVideoElementFrameProvider, createWhisperTranscriptionProvider, createZoomMath, deleteClipCommand, drawWaveform, frameToPixel, framesFromPixelDelta, letterboxRect, loadWaveform, mapWhisperOutput, mixdownToMono, moveClipCommand, moveDragStartFrame, needsSeek, pixelToFrame, placeClipCommand, selectTickStepSeconds, setClipTextCommand, snapPixel, splitClipCommand, toggleClipDisabledCommand, trimClipCommand, trimEndDrag, trimStartDrag };
|
|
858
|
+
export { type AddCaptionInput, type ApplySnapOptions, type AudioBufferLike, BrandMark, type BrandMarkProps, COMMAND_HISTORY_LIMIT, type ClipIdResolver, type ClipMoveCommit, type ClipTrimCommit, type CommandStack, DEFAULT_MAX_MEDIA_ELEMENTS, DEFAULT_TIMELINE_LABELS, DEFAULT_WHISPER_MODEL, type DeleteClipInput, type EditorTimelineState, type FrameRect, type LetterboxRect, type MediaElementPool, type MoveClipInput, type MoveDragInput, type PlaceClipInput, type PlaybackClock, type PlaybackClockConfig, type PooledElementLease, PreviewCanvas, type PreviewCanvasProps, SEEK_TIMEOUT_MS, SEEK_TOLERANCE_SECONDS, SEQUENCE_MEDIA_DRAG_TYPE, SequenceTimelineEditorLazy, type SetClipTextInput, SnapIndicatorLine, type SnapIndicatorLineProps, type SnapPoint, type SnapResult, type SplitClipInput, TimelineClipChip, type TimelineClipChipProps, type TimelineCommand, TimelineEditor, type TimelineEditorLabels, type TimelineEditorProps, TimelineEmptyState, type TimelineEmptyStateProps, TimelineGhostLanes, type TimelineGhostLanesProps, TimelinePlayhead, type TimelinePlayheadProps, TimelineRuler, type TimelineRulerProps, TimelineSmallScreenGate, type TimelineSmallScreenGateProps, type TimelineSnapPoint, TimelineTrackRow, type TimelineTrackRowProps, type ToggleClipDisabledInput, type TranscriptionProvider, type TranscriptionSegment, type TrimClipInput, type TrimEndDragInput, type TrimStartDragInput, type TrimStartDragResult, type VideoFrameProvider, type ViewportTransform, type WaveformData, type WhisperOutput, ZoomControl, type ZoomControlProps, type ZoomMath, type ZoomMathConfig, addCaptionCommand, applySnap, captionFontPx, chooseMoveSnap, classifyMediaUrl, clipChipGeometry, collectSnapPoints, compositeCommand, computeWaveform, containFitRect, createCommandStack, createImageFrameProvider, createMediaElementPool, createPlaybackClock, createVideoElementFrameProvider, createWhisperTranscriptionProvider, createZoomMath, deleteClipCommand, drawWaveform, frameToPixel, framesFromPixelDelta, letterboxRect, loadWaveform, mapWhisperOutput, mixdownToMono, moveClipCommand, moveDragStartFrame, needsSeek, pixelToFrame, placeClipCommand, selectTickStepSeconds, setClipTextCommand, snapPixel, splitClipCommand, toggleClipDisabledCommand, trimClipCommand, trimEndDrag, trimStartDrag };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BrandMark,
|
|
2
3
|
COMMAND_HISTORY_LIMIT,
|
|
3
4
|
DEFAULT_MAX_MEDIA_ELEMENTS,
|
|
5
|
+
DEFAULT_TIMELINE_LABELS,
|
|
4
6
|
PreviewCanvas,
|
|
5
7
|
SEEK_TIMEOUT_MS,
|
|
6
8
|
SEEK_TOLERANCE_SECONDS,
|
|
@@ -8,8 +10,11 @@ import {
|
|
|
8
10
|
SnapIndicatorLine,
|
|
9
11
|
TimelineClipChip,
|
|
10
12
|
TimelineEditor,
|
|
13
|
+
TimelineEmptyState,
|
|
14
|
+
TimelineGhostLanes,
|
|
11
15
|
TimelinePlayhead,
|
|
12
16
|
TimelineRuler,
|
|
17
|
+
TimelineSmallScreenGate,
|
|
13
18
|
TimelineTrackRow,
|
|
14
19
|
ZoomControl,
|
|
15
20
|
addCaptionCommand,
|
|
@@ -47,7 +52,7 @@ import {
|
|
|
47
52
|
trimClipCommand,
|
|
48
53
|
trimEndDrag,
|
|
49
54
|
trimStartDrag
|
|
50
|
-
} from "../chunk-
|
|
55
|
+
} from "../chunk-EX3LD5G6.js";
|
|
51
56
|
import "../chunk-ZYBWGSAZ.js";
|
|
52
57
|
|
|
53
58
|
// src/sequences-react/media/transcription.ts
|
|
@@ -182,10 +187,12 @@ function createWhisperTranscriptionProvider(opts) {
|
|
|
182
187
|
|
|
183
188
|
// src/sequences-react/lazy.tsx
|
|
184
189
|
import React from "react";
|
|
185
|
-
var SequenceTimelineEditorLazy = React.lazy(() => import("../TimelineEditor-
|
|
190
|
+
var SequenceTimelineEditorLazy = React.lazy(() => import("../TimelineEditor-SNOAGBV6.js"));
|
|
186
191
|
export {
|
|
192
|
+
BrandMark,
|
|
187
193
|
COMMAND_HISTORY_LIMIT,
|
|
188
194
|
DEFAULT_MAX_MEDIA_ELEMENTS,
|
|
195
|
+
DEFAULT_TIMELINE_LABELS,
|
|
189
196
|
DEFAULT_WHISPER_MODEL,
|
|
190
197
|
PreviewCanvas,
|
|
191
198
|
SEEK_TIMEOUT_MS,
|
|
@@ -195,8 +202,11 @@ export {
|
|
|
195
202
|
SnapIndicatorLine,
|
|
196
203
|
TimelineClipChip,
|
|
197
204
|
TimelineEditor,
|
|
205
|
+
TimelineEmptyState,
|
|
206
|
+
TimelineGhostLanes,
|
|
198
207
|
TimelinePlayhead,
|
|
199
208
|
TimelineRuler,
|
|
209
|
+
TimelineSmallScreenGate,
|
|
200
210
|
TimelineTrackRow,
|
|
201
211
|
ZoomControl,
|
|
202
212
|
addCaptionCommand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sequences-react/media/transcription.ts","../../src/sequences-react/lazy.tsx"],"sourcesContent":["/**\n * Whisper transcription behind the `TranscriptionProvider` seam, powered by\n * the OPTIONAL peer `@huggingface/transformers`. The peer is loaded with a\n * dynamic variable-specifier import inside `transcribe()` so bundlers leave\n * it external and apps that never transcribe never pay for it. `available`\n * is the sync UI affordance signal; `transcribe()` re-verifies with the real\n * import and is the source of truth.\n */\n\nimport type { TranscriptionProvider, TranscriptionSegment } from '../contracts'\nimport type { AudioBufferLike } from './waveform'\n\nexport const DEFAULT_WHISPER_MODEL = 'onnx-community/whisper-large-v3-turbo'\n\n/** Whisper models are trained on 16 kHz mono — decoding through a 16 kHz\n * OfflineAudioContext resamples any source rate in one step. */\nconst WHISPER_SAMPLE_RATE = 16_000\n\nconst TRANSCRIPTION_PEER = '@huggingface/transformers'\nconst PEER_MISSING_MESSAGE = 'transcription requires optional peer @huggingface/transformers'\n\ninterface WhisperChunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface WhisperOutput {\n text: string\n chunks?: WhisperChunk[]\n}\n\ntype WhisperPipeline = (\n audio: Float32Array,\n options: Record<string, unknown>,\n) => Promise<WhisperOutput | WhisperOutput[]>\n\ninterface TransformersProgressEvent {\n status: string\n progress?: number\n}\n\ninterface TransformersModule {\n pipeline(\n task: 'automatic-speech-recognition',\n model: string,\n options?: Record<string, unknown>,\n ): Promise<WhisperPipeline>\n}\n\n/** Mean-mixdown to mono. Single-channel buffers return the live channel data\n * without copying — callers must treat the result as read-only. */\nexport function mixdownToMono(buffer: AudioBufferLike): Float32Array {\n if (buffer.numberOfChannels < 1) throw new Error('audio buffer has no channels — cannot mix down')\n if (buffer.numberOfChannels === 1) return buffer.getChannelData(0)\n const mono = new Float32Array(buffer.length)\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const data = buffer.getChannelData(channel)\n if (data.length !== buffer.length) {\n throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`)\n }\n for (let i = 0; i < data.length; i++) {\n // in range by the length assertion above; casts silence noUncheckedIndexedAccess\n mono[i] = (mono[i] as number) + (data[i] as number)\n }\n }\n const scale = 1 / buffer.numberOfChannels\n for (let i = 0; i < mono.length; i++) {\n mono[i] = (mono[i] as number) * scale\n }\n return mono\n}\n\n/** Map whisper chunk output to contract segments. A `null` end timestamp is\n * whisper's \"ran past the end of the audio\" sentinel on the final chunk and\n * resolves to the audio duration. */\nexport function mapWhisperOutput(\n output: WhisperOutput | WhisperOutput[],\n durationSeconds: number,\n mediaUrl: string,\n): TranscriptionSegment[] {\n const first = Array.isArray(output) ? output[0] : output\n if (first === undefined) throw new Error(`whisper produced no output for ${mediaUrl}`)\n const chunks = first.chunks\n if (chunks === undefined || chunks.length === 0) {\n const text = first.text.trim()\n return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }]\n }\n const segments: TranscriptionSegment[] = []\n for (const chunk of chunks) {\n const text = chunk.text.trim()\n if (text.length === 0) continue\n const [start, end] = chunk.timestamp\n if (!Number.isFinite(start)) {\n throw new Error(`whisper returned a non-numeric start timestamp for \"${text}\" in ${mediaUrl}`)\n }\n segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end })\n }\n return segments\n}\n\nasync function loadTransformers(): Promise<TransformersModule> {\n try {\n // Variable specifier + ignore pragmas keep bundlers from statically\n // resolving an optional peer that may not be installed.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ TRANSCRIPTION_PEER)) as TransformersModule\n } catch (error) {\n throw new Error(PEER_MISSING_MESSAGE, { cause: error })\n }\n}\n\nasync function fetchMonoAudio(mediaUrl: string): Promise<{ samples: Float32Array; durationSeconds: number }> {\n const response = await fetch(mediaUrl)\n if (!response.ok) {\n throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`)\n }\n const bytes = await response.arrayBuffer()\n if (typeof OfflineAudioContext === 'undefined') {\n throw new Error('transcription requires Web Audio (OfflineAudioContext) — call transcribe() from a browser')\n }\n // decodeAudioData resamples to the context rate per the Web Audio spec, so\n // a 16 kHz context yields whisper-ready samples from any source rate.\n const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE)\n const decoded = await context.decodeAudioData(bytes)\n return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration }\n}\n\nexport function createWhisperTranscriptionProvider(opts?: { model?: string }): TranscriptionProvider {\n const model = opts?.model ?? DEFAULT_WHISPER_MODEL\n let availability: boolean | null = null\n let pipelinePromise: Promise<WhisperPipeline> | null = null\n\n const probeAvailability = (): boolean => {\n if (availability !== null) return availability\n const resolve = (import.meta as ImportMeta & { resolve?: (specifier: string) => string }).resolve\n if (typeof resolve !== 'function') {\n // No sync resolver in this runtime — report unavailable rather than\n // guess; transcribe() still attempts the real import either way.\n availability = false\n return availability\n }\n try {\n resolve.call(import.meta, TRANSCRIPTION_PEER)\n availability = true\n } catch {\n availability = false\n }\n return availability\n }\n\n const getPipeline = (\n transformers: TransformersModule,\n onProgress?: (fraction: number) => void,\n ): Promise<WhisperPipeline> => {\n if (pipelinePromise === null) {\n const device = typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm'\n pipelinePromise = transformers.pipeline('automatic-speech-recognition', model, {\n dtype: 'q4',\n device,\n // Model download progress only — it dwarfs inference time on first\n // use, and only the first transcribe ever sees a download.\n progress_callback: (event: TransformersProgressEvent) => {\n if (event.status === 'progress' && typeof event.progress === 'number' && onProgress !== undefined) {\n onProgress(Math.min(1, Math.max(0, event.progress / 100)))\n }\n },\n })\n // A failed load must not poison the cache — the next transcribe retries.\n pipelinePromise.catch(() => {\n pipelinePromise = null\n })\n }\n return pipelinePromise\n }\n\n return {\n get available() {\n return probeAvailability()\n },\n async transcribe(mediaUrl, transcribeOpts) {\n const transformers = await loadTransformers()\n // A successful import is ground truth and overrides a false probe.\n availability = true\n const audio = await fetchMonoAudio(mediaUrl)\n const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress)\n const options: Record<string, unknown> = {\n return_timestamps: true,\n chunk_length_s: 30,\n stride_length_s: 5,\n }\n if (transcribeOpts?.language !== undefined) options.language = transcribeOpts.language\n const output = await transcriber(audio.samples, options)\n const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl)\n transcribeOpts?.onProgress?.(1)\n return segments\n },\n }\n}\n","/**\n * Code-split entry for the timeline editor. The editor pulls in canvas\n * painting, waveform decode, and gesture machinery products only need on the\n * sequence route — `React.lazy` keeps it out of their main bundle. Mount\n * inside a `<Suspense>` boundary.\n */\n\nimport React from 'react'\n\nexport const SequenceTimelineEditorLazy = React.lazy(() => import('./components/TimelineEditor'))\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/sequences-react/media/transcription.ts","../../src/sequences-react/lazy.tsx"],"sourcesContent":["/**\n * Whisper transcription behind the `TranscriptionProvider` seam, powered by\n * the OPTIONAL peer `@huggingface/transformers`. The peer is loaded with a\n * dynamic variable-specifier import inside `transcribe()` so bundlers leave\n * it external and apps that never transcribe never pay for it. `available`\n * is the sync UI affordance signal; `transcribe()` re-verifies with the real\n * import and is the source of truth.\n */\n\nimport type { TranscriptionProvider, TranscriptionSegment } from '../contracts'\nimport type { AudioBufferLike } from './waveform'\n\nexport const DEFAULT_WHISPER_MODEL = 'onnx-community/whisper-large-v3-turbo'\n\n/** Whisper models are trained on 16 kHz mono — decoding through a 16 kHz\n * OfflineAudioContext resamples any source rate in one step. */\nconst WHISPER_SAMPLE_RATE = 16_000\n\nconst TRANSCRIPTION_PEER = '@huggingface/transformers'\nconst PEER_MISSING_MESSAGE = 'transcription requires optional peer @huggingface/transformers'\n\ninterface WhisperChunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface WhisperOutput {\n text: string\n chunks?: WhisperChunk[]\n}\n\ntype WhisperPipeline = (\n audio: Float32Array,\n options: Record<string, unknown>,\n) => Promise<WhisperOutput | WhisperOutput[]>\n\ninterface TransformersProgressEvent {\n status: string\n progress?: number\n}\n\ninterface TransformersModule {\n pipeline(\n task: 'automatic-speech-recognition',\n model: string,\n options?: Record<string, unknown>,\n ): Promise<WhisperPipeline>\n}\n\n/** Mean-mixdown to mono. Single-channel buffers return the live channel data\n * without copying — callers must treat the result as read-only. */\nexport function mixdownToMono(buffer: AudioBufferLike): Float32Array {\n if (buffer.numberOfChannels < 1) throw new Error('audio buffer has no channels — cannot mix down')\n if (buffer.numberOfChannels === 1) return buffer.getChannelData(0)\n const mono = new Float32Array(buffer.length)\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const data = buffer.getChannelData(channel)\n if (data.length !== buffer.length) {\n throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`)\n }\n for (let i = 0; i < data.length; i++) {\n // in range by the length assertion above; casts silence noUncheckedIndexedAccess\n mono[i] = (mono[i] as number) + (data[i] as number)\n }\n }\n const scale = 1 / buffer.numberOfChannels\n for (let i = 0; i < mono.length; i++) {\n mono[i] = (mono[i] as number) * scale\n }\n return mono\n}\n\n/** Map whisper chunk output to contract segments. A `null` end timestamp is\n * whisper's \"ran past the end of the audio\" sentinel on the final chunk and\n * resolves to the audio duration. */\nexport function mapWhisperOutput(\n output: WhisperOutput | WhisperOutput[],\n durationSeconds: number,\n mediaUrl: string,\n): TranscriptionSegment[] {\n const first = Array.isArray(output) ? output[0] : output\n if (first === undefined) throw new Error(`whisper produced no output for ${mediaUrl}`)\n const chunks = first.chunks\n if (chunks === undefined || chunks.length === 0) {\n const text = first.text.trim()\n return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }]\n }\n const segments: TranscriptionSegment[] = []\n for (const chunk of chunks) {\n const text = chunk.text.trim()\n if (text.length === 0) continue\n const [start, end] = chunk.timestamp\n if (!Number.isFinite(start)) {\n throw new Error(`whisper returned a non-numeric start timestamp for \"${text}\" in ${mediaUrl}`)\n }\n segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end })\n }\n return segments\n}\n\nasync function loadTransformers(): Promise<TransformersModule> {\n try {\n // Variable specifier + ignore pragmas keep bundlers from statically\n // resolving an optional peer that may not be installed.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ TRANSCRIPTION_PEER)) as TransformersModule\n } catch (error) {\n throw new Error(PEER_MISSING_MESSAGE, { cause: error })\n }\n}\n\nasync function fetchMonoAudio(mediaUrl: string): Promise<{ samples: Float32Array; durationSeconds: number }> {\n const response = await fetch(mediaUrl)\n if (!response.ok) {\n throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`)\n }\n const bytes = await response.arrayBuffer()\n if (typeof OfflineAudioContext === 'undefined') {\n throw new Error('transcription requires Web Audio (OfflineAudioContext) — call transcribe() from a browser')\n }\n // decodeAudioData resamples to the context rate per the Web Audio spec, so\n // a 16 kHz context yields whisper-ready samples from any source rate.\n const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE)\n const decoded = await context.decodeAudioData(bytes)\n return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration }\n}\n\nexport function createWhisperTranscriptionProvider(opts?: { model?: string }): TranscriptionProvider {\n const model = opts?.model ?? DEFAULT_WHISPER_MODEL\n let availability: boolean | null = null\n let pipelinePromise: Promise<WhisperPipeline> | null = null\n\n const probeAvailability = (): boolean => {\n if (availability !== null) return availability\n const resolve = (import.meta as ImportMeta & { resolve?: (specifier: string) => string }).resolve\n if (typeof resolve !== 'function') {\n // No sync resolver in this runtime — report unavailable rather than\n // guess; transcribe() still attempts the real import either way.\n availability = false\n return availability\n }\n try {\n resolve.call(import.meta, TRANSCRIPTION_PEER)\n availability = true\n } catch {\n availability = false\n }\n return availability\n }\n\n const getPipeline = (\n transformers: TransformersModule,\n onProgress?: (fraction: number) => void,\n ): Promise<WhisperPipeline> => {\n if (pipelinePromise === null) {\n const device = typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm'\n pipelinePromise = transformers.pipeline('automatic-speech-recognition', model, {\n dtype: 'q4',\n device,\n // Model download progress only — it dwarfs inference time on first\n // use, and only the first transcribe ever sees a download.\n progress_callback: (event: TransformersProgressEvent) => {\n if (event.status === 'progress' && typeof event.progress === 'number' && onProgress !== undefined) {\n onProgress(Math.min(1, Math.max(0, event.progress / 100)))\n }\n },\n })\n // A failed load must not poison the cache — the next transcribe retries.\n pipelinePromise.catch(() => {\n pipelinePromise = null\n })\n }\n return pipelinePromise\n }\n\n return {\n get available() {\n return probeAvailability()\n },\n async transcribe(mediaUrl, transcribeOpts) {\n const transformers = await loadTransformers()\n // A successful import is ground truth and overrides a false probe.\n availability = true\n const audio = await fetchMonoAudio(mediaUrl)\n const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress)\n const options: Record<string, unknown> = {\n return_timestamps: true,\n chunk_length_s: 30,\n stride_length_s: 5,\n }\n if (transcribeOpts?.language !== undefined) options.language = transcribeOpts.language\n const output = await transcriber(audio.samples, options)\n const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl)\n transcribeOpts?.onProgress?.(1)\n return segments\n },\n }\n}\n","/**\n * Code-split entry for the timeline editor. The editor pulls in canvas\n * painting, waveform decode, and gesture machinery products only need on the\n * sequence route — `React.lazy` keeps it out of their main bundle. Mount\n * inside a `<Suspense>` boundary.\n */\n\nimport React from 'react'\n\nexport const SequenceTimelineEditorLazy = React.lazy(() => import('./components/TimelineEditor'))\n"],"mappings":"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","names":[]}
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
interface StudioIntegrationConnection {
|
|
2
2
|
status: string;
|
|
3
3
|
providerId: string;
|
|
4
|
-
connectorId
|
|
4
|
+
connectorId?: string;
|
|
5
5
|
}
|
|
6
6
|
type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription';
|
|
7
7
|
type GenerationStatus = 'pending' | 'running' | 'succeeded' | 'failed';
|
package/dist/studio/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
relativeTime,
|
|
25
25
|
selectedModelsWithDefaults,
|
|
26
26
|
userSafeGenerationMessage
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-MFI4ZM7W.js";
|
|
28
28
|
|
|
29
29
|
// src/studio-react/studio-workspace.tsx
|
|
30
30
|
import { useRef as useRef3, useState as useState3 } from "react";
|
package/dist/teams/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateInviteToken,
|
|
3
|
+
isInviteTokenShape,
|
|
4
|
+
validateInviteToken
|
|
5
|
+
} from "../chunk-3SVAA3MA.js";
|
|
1
6
|
import {
|
|
2
7
|
INVITATION_EXPIRY_DAYS,
|
|
3
8
|
generateInvitationToken,
|
|
@@ -7,11 +12,6 @@ import {
|
|
|
7
12
|
parseInvitationPermission,
|
|
8
13
|
renderInvitationEmail
|
|
9
14
|
} from "../chunk-WEBBJBDH.js";
|
|
10
|
-
import {
|
|
11
|
-
generateInviteToken,
|
|
12
|
-
isInviteTokenShape,
|
|
13
|
-
validateInviteToken
|
|
14
|
-
} from "../chunk-3SVAA3MA.js";
|
|
15
15
|
import {
|
|
16
16
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
17
17
|
ORGANIZATION_ROLES,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SeatLimitError
|
|
3
3
|
} from "../chunk-SWUVTGMR.js";
|
|
4
|
+
import "../chunk-3SVAA3MA.js";
|
|
4
5
|
import {
|
|
5
6
|
generateInvitationToken,
|
|
6
7
|
getInvitationExpiresAt,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
normalizeInvitationEmail,
|
|
9
10
|
parseInvitationPermission
|
|
10
11
|
} from "../chunk-WEBBJBDH.js";
|
|
11
|
-
import "../chunk-3SVAA3MA.js";
|
|
12
12
|
import {
|
|
13
13
|
hasWorkspaceRole
|
|
14
14
|
} from "../chunk-63CE7FEZ.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SendInvitationEmailSeam } from './invitations-api.js';
|
|
2
|
+
import '../invitations-CZ5G2tIn.js';
|
|
3
|
+
import '../roles-BC1n4t37.js';
|
|
4
|
+
import './members-api.js';
|
|
5
|
+
import '../access-BZ81Btt5.js';
|
|
6
|
+
import 'drizzle-orm/sqlite-core';
|
|
7
|
+
import '../invitations-schema-DaMO4hZE.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Opt-in Resend transport for the teams invitations `sendInvitationEmail` seam.
|
|
11
|
+
* Most adopters back the seam with Resend and hand-roll the same wrapper — and
|
|
12
|
+
* the same mistake: `resend.emails.send()` returns `{ data, error }` and does NOT
|
|
13
|
+
* throw on an API-level failure (unverified domain, rate limit, bad recipient),
|
|
14
|
+
* so a `try/catch` alone records a failed send as a success. This helper sends
|
|
15
|
+
* through the shared `renderInvitationEmail` template and returns a typed failure
|
|
16
|
+
* on BOTH a thrown error AND a non-null `result.error`.
|
|
17
|
+
*
|
|
18
|
+
* `resend` is an OPTIONAL peer, imported only here: apps not on Resend keep
|
|
19
|
+
* passing a raw seam, and the package core never pulls a mail dependency.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface ResendInvitationSenderOptions {
|
|
23
|
+
/** RFC-5322 From header, e.g. `GTM Agent <noreply@gtm.tangle.tools>`. */
|
|
24
|
+
from: string;
|
|
25
|
+
/** Resend API key. Defaults to `process.env.RESEND_API_KEY`. */
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a `sendInvitationEmail` seam backed by Resend. Wire it into
|
|
30
|
+
* `createInvitationsApi({ sendInvitationEmail: createResendInvitationSender({ from }) })`.
|
|
31
|
+
* The client is built lazily on first send; with no key the seam fails typed
|
|
32
|
+
* (the invitation is still created — emailStatus becomes 'failed').
|
|
33
|
+
*/
|
|
34
|
+
declare function createResendInvitationSender(opts: ResendInvitationSenderOptions): SendInvitationEmailSeam;
|
|
35
|
+
|
|
36
|
+
export { type ResendInvitationSenderOptions, createResendInvitationSender };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderInvitationEmail
|
|
3
|
+
} from "../chunk-WEBBJBDH.js";
|
|
4
|
+
|
|
5
|
+
// src/teams/resend.ts
|
|
6
|
+
import { Resend } from "resend";
|
|
7
|
+
function createResendInvitationSender(opts) {
|
|
8
|
+
let client = null;
|
|
9
|
+
function getClient() {
|
|
10
|
+
if (client) return client;
|
|
11
|
+
const key = opts.apiKey ?? (typeof process !== "undefined" ? process.env.RESEND_API_KEY : void 0);
|
|
12
|
+
if (!key) return null;
|
|
13
|
+
client = new Resend(key);
|
|
14
|
+
return client;
|
|
15
|
+
}
|
|
16
|
+
return async (input) => {
|
|
17
|
+
const resend = getClient();
|
|
18
|
+
if (!resend) return { succeeded: false, error: "RESEND_API_KEY is not configured" };
|
|
19
|
+
const msg = renderInvitationEmail(input, { fromAddress: opts.from });
|
|
20
|
+
try {
|
|
21
|
+
const result = await resend.emails.send({
|
|
22
|
+
from: msg.from,
|
|
23
|
+
to: input.to,
|
|
24
|
+
subject: msg.subject,
|
|
25
|
+
html: msg.html,
|
|
26
|
+
text: msg.text
|
|
27
|
+
});
|
|
28
|
+
if (result.error) return { succeeded: false, error: result.error.message };
|
|
29
|
+
return { succeeded: true };
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return { succeeded: false, error: err instanceof Error ? err.message : "Invitation email failed to send" };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
createResendInvitationSender
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=resend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/teams/resend.ts"],"sourcesContent":["/**\n * Opt-in Resend transport for the teams invitations `sendInvitationEmail` seam.\n * Most adopters back the seam with Resend and hand-roll the same wrapper — and\n * the same mistake: `resend.emails.send()` returns `{ data, error }` and does NOT\n * throw on an API-level failure (unverified domain, rate limit, bad recipient),\n * so a `try/catch` alone records a failed send as a success. This helper sends\n * through the shared `renderInvitationEmail` template and returns a typed failure\n * on BOTH a thrown error AND a non-null `result.error`.\n *\n * `resend` is an OPTIONAL peer, imported only here: apps not on Resend keep\n * passing a raw seam, and the package core never pulls a mail dependency.\n */\n\nimport { Resend } from 'resend'\nimport { renderInvitationEmail } from './invitations'\nimport type { SendInvitationEmailSeam } from './invitations-api'\n\nexport interface ResendInvitationSenderOptions {\n /** RFC-5322 From header, e.g. `GTM Agent <noreply@gtm.tangle.tools>`. */\n from: string\n /** Resend API key. Defaults to `process.env.RESEND_API_KEY`. */\n apiKey?: string\n}\n\n/**\n * Build a `sendInvitationEmail` seam backed by Resend. Wire it into\n * `createInvitationsApi({ sendInvitationEmail: createResendInvitationSender({ from }) })`.\n * The client is built lazily on first send; with no key the seam fails typed\n * (the invitation is still created — emailStatus becomes 'failed').\n */\nexport function createResendInvitationSender(opts: ResendInvitationSenderOptions): SendInvitationEmailSeam {\n let client: Resend | null = null\n\n function getClient(): Resend | null {\n if (client) return client\n const key = opts.apiKey ?? (typeof process !== 'undefined' ? process.env.RESEND_API_KEY : undefined)\n if (!key) return null\n client = new Resend(key)\n return client\n }\n\n return async (input) => {\n const resend = getClient()\n if (!resend) return { succeeded: false, error: 'RESEND_API_KEY is not configured' }\n\n const msg = renderInvitationEmail(input, { fromAddress: opts.from })\n try {\n const result = await resend.emails.send({\n from: msg.from,\n to: input.to,\n subject: msg.subject,\n html: msg.html,\n text: msg.text,\n })\n // Resend reports API-level failures in `result.error` WITHOUT throwing — a\n // try/catch alone would mark a failed send as a success.\n if (result.error) return { succeeded: false, error: result.error.message }\n return { succeeded: true }\n } catch (err) {\n return { succeeded: false, error: err instanceof Error ? err.message : 'Invitation email failed to send' }\n }\n }\n}\n"],"mappings":";;;;;AAaA,SAAS,cAAc;AAiBhB,SAAS,6BAA6B,MAA8D;AACzG,MAAI,SAAwB;AAE5B,WAAS,YAA2B;AAClC,QAAI,OAAQ,QAAO;AACnB,UAAM,MAAM,KAAK,WAAW,OAAO,YAAY,cAAc,QAAQ,IAAI,iBAAiB;AAC1F,QAAI,CAAC,IAAK,QAAO;AACjB,aAAS,IAAI,OAAO,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,UAAU;AACtB,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ,QAAO,EAAE,WAAW,OAAO,OAAO,mCAAmC;AAElF,UAAM,MAAM,sBAAsB,OAAO,EAAE,aAAa,KAAK,KAAK,CAAC;AACnE,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,OAAO,KAAK;AAAA,QACtC,MAAM,IAAI;AAAA,QACV,IAAI,MAAM;AAAA,QACV,SAAS,IAAI;AAAA,QACb,MAAM,IAAI;AAAA,QACV,MAAM,IAAI;AAAA,MACZ,CAAC;AAGD,UAAI,OAAO,MAAO,QAAO,EAAE,WAAW,OAAO,OAAO,OAAO,MAAM,QAAQ;AACzE,aAAO,EAAE,WAAW,KAAK;AAAA,IAC3B,SAAS,KAAK;AACZ,aAAO,EAAE,WAAW,OAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,kCAAkC;AAAA,IAC3G;AAAA,EACF;AACF;","names":[]}
|