@cueframe/kernel 0.0.1 → 0.1.0
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/_members/account-model/testing/conversationStoreContract.d.ts +1 -1
- package/dist/_members/authoring/providers/resourceApi.d.ts +1 -1
- package/dist/_members/authoring/transpileComponent.d.ts +1 -1
- package/dist/_members/cloud-import/CloudImportService.d.ts +35 -0
- package/dist/_members/cloud-import/ImportProvider.d.ts +73 -0
- package/dist/_members/cloud-import/MediaItemPreprocessor.d.ts +27 -0
- package/dist/_members/cloud-import/chunk-XKO6ZSWI.js +162 -0
- package/dist/_members/cloud-import/chunk-XKO6ZSWI.js.map +1 -0
- package/dist/_members/cloud-import/cloudConfig.d.ts +23 -0
- package/dist/_members/cloud-import/index.d.ts +9 -0
- package/dist/_members/cloud-import/index.js +241 -0
- package/dist/_members/cloud-import/index.js.map +1 -0
- package/dist/_members/cloud-import/multipartUpload.d.ts +12 -0
- package/dist/_members/cloud-import/multipartUpload.js +7 -0
- package/dist/_members/cloud-import/multipartUpload.js.map +1 -0
- package/dist/_members/composition/ops/compositionOps.d.ts +1 -1
- package/dist/_members/composition/ops/types.d.ts +1 -1
- package/dist/_members/composition/timeline/placement.d.ts +1 -1
- package/dist/_members/composition/validators/composition.d.ts +1 -1
- package/dist/_members/composition-ops/index.d.ts +1 -1
- package/dist/_members/composition-ops/opRecorder.d.ts +1 -1
- package/dist/_members/convex-api/api.d.ts +5 -0
- package/dist/_members/convex-api/dataModel.d.ts +6 -0
- package/dist/_members/core/render/exportSettings.d.ts +1 -1
- package/dist/_members/mcp/curation.d.ts +1 -1
- package/dist/_members/plane/conformance.d.ts +1 -1
- package/dist/_members/render-harness/localRender.d.ts +1 -1
- package/dist/animate.js +828 -79187
- package/dist/authoring/build-tools.js +8 -11
- package/dist/authoring/context.js +5 -5
- package/dist/authoring/models.js +1 -2
- package/dist/authoring/prompts.js +1 -1
- package/dist/authoring/tools.js +7 -7
- package/dist/authoring/transpileComponent.js +3 -2220
- package/dist/authoring.js +27 -29
- package/dist/brand-derivation.js +1 -1
- package/dist/chunk-6IU2UQI5.js +107 -0
- package/dist/{chunk-N67PDTKQ.js → chunk-ABL557OT.js} +1 -1
- package/dist/{chunk-DSNG6WLU.js → chunk-AYVJXDLB.js} +1 -4
- package/dist/{chunk-G3PBH53P.js → chunk-COYMZ76S.js} +1 -1
- package/dist/chunk-DSMLHYJ4.js +82 -0
- package/dist/{chunk-BRKJKB33.js → chunk-FYZVH3MC.js} +1 -1
- package/dist/{chunk-MVEE6HEC.js → chunk-GA23TRNR.js} +5 -7
- package/dist/{chunk-OIRUO5P7.js → chunk-GEA7E2FL.js} +1 -1
- package/dist/chunk-HCCBDJLW.js +68 -0
- package/dist/{chunk-QLAQHR3L.js → chunk-IBYQG46O.js} +1 -1
- package/dist/chunk-JQMPHKLU.js +126 -0
- package/dist/{chunk-ELLSET2N.js → chunk-LM4WDUQJ.js} +3 -6
- package/dist/{chunk-UEI3NLNK.js → chunk-MHOQTPOB.js} +1 -4
- package/dist/{chunk-ATAF24QG.js → chunk-MXFAADCQ.js} +1 -1
- package/dist/{chunk-TULRCY6U.js → chunk-NFEGPWGS.js} +1 -1
- package/dist/{chunk-MC5DLSEV.js → chunk-ODL7GQJG.js} +14 -14
- package/dist/{chunk-WZTTZHNR.js → chunk-OLFOPQCA.js} +12 -9
- package/dist/{chunk-P43IE4WB.js → chunk-P727GZVO.js} +2 -4
- package/dist/{chunk-S3P5BFFL.js → chunk-PMY24FUD.js} +2 -4
- package/dist/chunk-RF7U3SCL.js +606 -0
- package/dist/{chunk-JD77Y2WM.js → chunk-SW336CJP.js} +1 -4
- package/dist/{chunk-M3I5LMAB.js → chunk-UY4SWJVE.js} +1 -1
- package/dist/{chunk-52AHXNKM.js → chunk-VOO5FQSS.js} +5 -5
- package/dist/{chunk-NV4I6GJV.js → chunk-XWKZB5U5.js} +4 -4
- package/dist/{chunk-6QJ3V3VS.js → chunk-YX2GSJK7.js} +1 -1
- package/dist/{chunk-5I7VDCAC.js → chunk-ZB3C3HM7.js} +1 -1
- package/dist/chunk-ZT7MHRQU.js +104 -0
- package/dist/cloud-import/multipart.d.ts +1 -0
- package/dist/cloud-import/multipart.js +8 -0
- package/dist/cloud-import.d.ts +1 -0
- package/dist/cloud-import.js +234 -0
- package/dist/compose/renderer.js +18 -21
- package/dist/compose.js +18 -21
- package/dist/composition/graphics-plan.js +3 -3
- package/dist/composition/ops.js +4 -4
- package/dist/composition/primitive-catalog.js +1 -1
- package/dist/composition/projection.js +3 -3
- package/dist/composition/validators.js +2 -2
- package/dist/composition-ops.js +12 -12
- package/dist/composition.js +5 -5
- package/dist/{context-CEAKQ5T7.js → context-OKX3HHFS.js} +5 -5
- package/dist/convex-api/dataModel.d.ts +1 -0
- package/dist/convex-api/dataModel.js +0 -0
- package/dist/convex-api.d.ts +1 -0
- package/dist/convex-api.js +8 -0
- package/dist/core/render.js +2 -2
- package/dist/core.js +2 -2
- package/dist/mcp/adapter.js +1 -5
- package/dist/mcp/server.js +1 -2
- package/dist/mcp.js +2 -95
- package/dist/media-model/media-kind.js +9 -98
- package/dist/nle-export/xml.js +5 -22799
- package/dist/nle-export.js +18 -21
- package/dist/primitives/backgrounds.js +2 -3
- package/dist/primitives/captions.js +4 -243
- package/dist/primitives/effects.js +2 -3
- package/dist/primitives/hero-text.js +2 -3
- package/dist/primitives/layouts.js +2 -3
- package/dist/primitives/lib/index.js +8 -10
- package/dist/primitives/lib/preview.js +1 -2
- package/dist/primitives/lib/schema.js +1 -2
- package/dist/primitives/lib/text/fitText.js +1 -2
- package/dist/primitives/lib.js +8 -10
- package/dist/primitives/scenes.js +1 -2
- package/dist/primitives/text.js +2 -3
- package/dist/primitives/ui-blocks.js +4 -5
- package/dist/primitives.js +19 -21
- package/package.json +123 -13
- package/dist/TextureLoader-UQOFUSAP.js +0 -422
- package/dist/VideoTexture-AQWSPIJG.js +0 -62
- package/dist/chunk-7GISRVON.js +0 -14176
- package/dist/chunk-DIBOJJMU.js +0 -16838
- package/dist/chunk-EAUSEGB7.js +0 -420
- package/dist/chunk-FUWJOVVK.js +0 -3633
- package/dist/chunk-G75E7YA4.js +0 -11883
- package/dist/chunk-JCZQT7K3.js +0 -3266
- package/dist/chunk-KF5ETMKB.js +0 -544
- package/dist/chunk-NDKERSXY.js +0 -1296
- package/dist/chunk-V43Y7IE4.js +0 -3621
- package/dist/chunk-VIVQHIGZ.js +0 -4319
- package/dist/chunk-YA3OR2KK.js +0 -249
- package/dist/hls-HD4C5G6W.js +0 -33000
- package/dist/token-RGFIT24D.js +0 -69
- package/dist/token-util-GPLSGYVO.js +0 -5
- package/dist/vision_bundle-5Y376MAR.js +0 -4084
- package/dist/{chunk-6CMVBTTP.js → chunk-YIOY2XEO.js} +4 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* THE CONVERSATION STORE CONTRACT — one executable statement, every store.
|
|
3
3
|
*
|
|
4
4
|
* A Director thread is persisted by TWO independent implementations: the
|
|
5
|
-
* desktop's sqlite (`
|
|
5
|
+
* desktop's sqlite (`apps/studio/src-tauri/src/database/conversations.rs`) and the
|
|
6
6
|
* org's Convex documents (`the backend conversation store`), reached through
|
|
7
7
|
* an adapter each (`platform/tauri.ts`, `packages/web/adapter.ts`). A third
|
|
8
8
|
* "implementation" exists in the test double the app's own tests run against.
|
|
@@ -2,7 +2,7 @@ import type { FontResult, LottieResult } from '../types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Public-API resource clients — pure HTTP, no gateway auth.
|
|
4
4
|
*
|
|
5
|
-
* Lifted from `
|
|
5
|
+
* Lifted from `apps/studio/src/llm/resourceApi.ts` so both the desktop Tauri
|
|
6
6
|
* app and the server-side Temporal director worker can call the same
|
|
7
7
|
* Google Fonts + LottieFiles clients. Gateway-routed search (Pexels,
|
|
8
8
|
* Pixabay, Freesound, Klipy) lives in `../resources.ts` — that module
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* remotion / animate as endowments — and (b) assign `exports.default = <Component>`.
|
|
12
12
|
*
|
|
13
13
|
* This is the Node/native-esbuild sibling of the desktop `transpileJsx`
|
|
14
|
-
* (
|
|
14
|
+
* (apps/studio/src/llm/transpile.ts, esbuild-wasm) and the director's
|
|
15
15
|
* `serverTranspiler` (activities/director.ts). All three share ONE transpile
|
|
16
16
|
* config — the SSOT `ESBUILD_JSX_OPTIONS` + `postProcessForSandbox` in
|
|
17
17
|
* ./transpileHelper — so a live component transpiled here is byte-identical to
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Singleton Uppy service for imports (local + cloud).
|
|
3
|
+
*
|
|
4
|
+
* Uses @uppy/aws-s3 for multipart uploads directly to R2 via the
|
|
5
|
+
* upload-signing CF Worker. Replaces the old TUS-based upload path.
|
|
6
|
+
*
|
|
7
|
+
* Owns the Uppy instance lifecycle (headless — no Dashboard plugin).
|
|
8
|
+
* React integration is handled by ImportProvider (single mount at app root).
|
|
9
|
+
* MediaItem pre-creation is handled by MediaItemPreprocessor (Uppy plugin).
|
|
10
|
+
*/
|
|
11
|
+
import Uppy from '@uppy/core';
|
|
12
|
+
import { MediaItemPreprocessor } from './MediaItemPreprocessor.js';
|
|
13
|
+
interface CloudImportConfig {
|
|
14
|
+
uploadSigningUrl: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CloudUploadResult {
|
|
17
|
+
uploadURL: string;
|
|
18
|
+
fileName: string;
|
|
19
|
+
fileSize: number;
|
|
20
|
+
}
|
|
21
|
+
declare class CloudImportService {
|
|
22
|
+
private uppy;
|
|
23
|
+
private config;
|
|
24
|
+
/** Lazy-init: create Uppy + plugins on first use. */
|
|
25
|
+
init(config: CloudImportConfig): Uppy;
|
|
26
|
+
/** Get the Uppy instance (must call init() first). */
|
|
27
|
+
getUppy(): Uppy;
|
|
28
|
+
/** Get the MediaItemPreprocessor plugin. */
|
|
29
|
+
getPreprocessor(): MediaItemPreprocessor;
|
|
30
|
+
/** Update upload metadata (uploadToken, userId, projectId). */
|
|
31
|
+
setMeta(meta: Record<string, string>): void;
|
|
32
|
+
}
|
|
33
|
+
/** Module-level singleton. */
|
|
34
|
+
export declare const cloudImportService: CloudImportService;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImportProvider — single mount at app root.
|
|
3
|
+
*
|
|
4
|
+
* Initializes the CloudImportService, wires auth callbacks, and provides
|
|
5
|
+
* the useImport() hook to child components. Wraps children in Uppy's
|
|
6
|
+
* UppyContextProvider so headless hooks (useDropzone, useUppyState, etc.)
|
|
7
|
+
* work anywhere in the tree.
|
|
8
|
+
*
|
|
9
|
+
* The import dialog UI is injected via `dialogSlot` to avoid circular
|
|
10
|
+
* dependency between @cueframe/cloud-import and @cueframe/pro.
|
|
11
|
+
*/
|
|
12
|
+
import { type ReactNode } from 'react';
|
|
13
|
+
import type Uppy from '@uppy/core';
|
|
14
|
+
import type { CloudUploadResult } from './CloudImportService.js';
|
|
15
|
+
interface ImportContextValue {
|
|
16
|
+
openModal: () => void;
|
|
17
|
+
closeModal: () => void;
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
uppy: Uppy;
|
|
20
|
+
/**
|
|
21
|
+
* Can this lane actually take an upload right now?
|
|
22
|
+
*
|
|
23
|
+
* A cloud import needs an identity: `getUploadMeta` mints an org-scoped
|
|
24
|
+
* upload token, so with none the modal opens onto a request that can only
|
|
25
|
+
* fail. This is DATA because the provider is now a fixture of the tree — it
|
|
26
|
+
* mounts for the life of the desktop process, signed in or not, so its
|
|
27
|
+
* PRESENCE stopped being the answer to this question (it was, back when the
|
|
28
|
+
* whole cloud stack mounted only inside a confirmed auth handshake).
|
|
29
|
+
*/
|
|
30
|
+
available: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ImportProviderProps {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
/** Whether this lane can take an upload — see `ImportContextValue.available`.
|
|
35
|
+
* Required, with no default: the one thing a caller must not forget is the
|
|
36
|
+
* fact that decides whether a control is offered at all. */
|
|
37
|
+
available: boolean;
|
|
38
|
+
/** React element to render the import dialog (e.g. <ImportDialog />) */
|
|
39
|
+
dialogSlot?: ReactNode;
|
|
40
|
+
uploadSigningUrl: string;
|
|
41
|
+
/** Called before opening the modal — returns upload metadata (token, userId, etc). */
|
|
42
|
+
getUploadMeta?: () => Promise<Record<string, string>>;
|
|
43
|
+
/** Pre-create a mediaItem — called by the preprocessor plugin before upload starts. */
|
|
44
|
+
createMediaItem?: (file: {
|
|
45
|
+
name: string;
|
|
46
|
+
size: number;
|
|
47
|
+
mimeType: string;
|
|
48
|
+
}) => Promise<string>;
|
|
49
|
+
onUploadComplete?: (results: CloudUploadResult[]) => void;
|
|
50
|
+
onError?: (error: Error) => void;
|
|
51
|
+
}
|
|
52
|
+
export declare function ImportProvider({ children, available, dialogSlot, uploadSigningUrl, getUploadMeta, createMediaItem, onUploadComplete, onError, }: ImportProviderProps): import("react").JSX.Element;
|
|
53
|
+
/** Hook to access the import modal and Uppy instance. Must be used inside <ImportProvider>. */
|
|
54
|
+
export declare function useImport(): ImportContextValue;
|
|
55
|
+
/**
|
|
56
|
+
* The context WHEN THE LANE CAN BE USED, and `null` otherwise — for surfaces
|
|
57
|
+
* that offer the cloud modal only where it works and fall back where it does
|
|
58
|
+
* not (the pro ImportButton, which drops to the local file picker).
|
|
59
|
+
*
|
|
60
|
+
* It used to answer presence alone, and presence used to be the same fact:
|
|
61
|
+
* the provider mounted only inside a confirmed auth handshake. It mounts for
|
|
62
|
+
* the life of the desktop process now (Task 11 — a provider that appears and
|
|
63
|
+
* disappears re-parents the app), so presence would offer a cloud modal to a
|
|
64
|
+
* signed-out user whose upload-token request can only fail: a control that
|
|
65
|
+
* looks alive and silently does nothing.
|
|
66
|
+
*
|
|
67
|
+
* Still ONE fact, read from the tree rather than mirrored: the alternative —
|
|
68
|
+
* a caller re-deriving `isDesktop && cloudEnabled && signedIn` — is the drift
|
|
69
|
+
* class that crashed the desktop ("useImport must be used within
|
|
70
|
+
* <ImportProvider>") whenever the two copies disagreed.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useImportIfAvailable(): ImportContextValue | null;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uppy preprocessor plugin that pre-creates a mediaItem in the backend
|
|
3
|
+
* before the upload starts. This guarantees convexMediaItemId is in the
|
|
4
|
+
* upload metadata before the first byte goes out — no race condition.
|
|
5
|
+
*
|
|
6
|
+
* The actual creation function is injected via setCreateFn() so the plugin
|
|
7
|
+
* stays decoupled from Convex/auth concerns.
|
|
8
|
+
*/
|
|
9
|
+
import { BasePlugin } from '@uppy/core';
|
|
10
|
+
import type { Uppy, Meta } from '@uppy/core';
|
|
11
|
+
type CreateMediaItemFn = (file: {
|
|
12
|
+
name: string;
|
|
13
|
+
size: number;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
}) => Promise<string>;
|
|
16
|
+
export declare class MediaItemPreprocessor extends BasePlugin<Record<string, never>, Meta, Record<string, never>> {
|
|
17
|
+
static pluginId: string;
|
|
18
|
+
private createFn;
|
|
19
|
+
private preprocessBound;
|
|
20
|
+
constructor(uppy: Uppy, opts?: Record<string, never>);
|
|
21
|
+
/** Inject the creation function (called from ImportProvider). */
|
|
22
|
+
setCreateFn(fn: CreateMediaItemFn | null): void;
|
|
23
|
+
install(): void;
|
|
24
|
+
uninstall(): void;
|
|
25
|
+
private preprocess;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// ../data-models/media-model/src/media-kind.ts
|
|
6
|
+
var IMAGE_CONTENT_TYPES = ["image/png", "image/jpeg", "image/webp"];
|
|
7
|
+
var ALLOWED_CONTENT_TYPES = [
|
|
8
|
+
"video/mp4",
|
|
9
|
+
"video/quicktime",
|
|
10
|
+
"video/x-matroska",
|
|
11
|
+
"video/webm",
|
|
12
|
+
"audio/mpeg",
|
|
13
|
+
"audio/wav",
|
|
14
|
+
"audio/x-wav",
|
|
15
|
+
"audio/mp4",
|
|
16
|
+
"audio/aac",
|
|
17
|
+
"image/png",
|
|
18
|
+
"image/jpeg",
|
|
19
|
+
"image/webp",
|
|
20
|
+
"image/gif",
|
|
21
|
+
"image/svg+xml",
|
|
22
|
+
"application/json",
|
|
23
|
+
"font/ttf",
|
|
24
|
+
"font/otf",
|
|
25
|
+
"font/woff",
|
|
26
|
+
"font/woff2"
|
|
27
|
+
];
|
|
28
|
+
function mediaKind(contentType) {
|
|
29
|
+
const ct = (contentType || "").toLowerCase();
|
|
30
|
+
if (ct.startsWith("video/")) return "video";
|
|
31
|
+
if (ct.startsWith("audio/")) return "audio";
|
|
32
|
+
if (IMAGE_CONTENT_TYPES.includes(ct)) return "image";
|
|
33
|
+
return "opaque";
|
|
34
|
+
}
|
|
35
|
+
var EXT = {
|
|
36
|
+
"video/mp4": "mp4",
|
|
37
|
+
"audio/mp4": "mp4",
|
|
38
|
+
"video/quicktime": "mov",
|
|
39
|
+
"video/x-matroska": "mkv",
|
|
40
|
+
"video/webm": "webm",
|
|
41
|
+
"video/x-msvideo": "avi",
|
|
42
|
+
"audio/mpeg": "mp3",
|
|
43
|
+
"audio/wav": "wav",
|
|
44
|
+
"audio/x-wav": "wav",
|
|
45
|
+
"audio/aac": "aac",
|
|
46
|
+
"audio/ogg": "ogg",
|
|
47
|
+
"audio/flac": "flac",
|
|
48
|
+
"image/png": "png",
|
|
49
|
+
"image/jpeg": "jpg",
|
|
50
|
+
"image/webp": "webp",
|
|
51
|
+
"font/ttf": "ttf",
|
|
52
|
+
"font/otf": "otf",
|
|
53
|
+
"font/woff": "woff",
|
|
54
|
+
"font/woff2": "woff2",
|
|
55
|
+
"application/json": "json",
|
|
56
|
+
"image/svg+xml": "svg",
|
|
57
|
+
"image/gif": "gif"
|
|
58
|
+
};
|
|
59
|
+
var CONTENT_TYPE_ALIASES = {
|
|
60
|
+
"audio/mp3": "audio/mpeg",
|
|
61
|
+
"audio/x-mp3": "audio/mpeg",
|
|
62
|
+
"audio/x-mpeg": "audio/mpeg",
|
|
63
|
+
"audio/mpeg3": "audio/mpeg",
|
|
64
|
+
"audio/x-mpeg-3": "audio/mpeg",
|
|
65
|
+
"audio/x-m4a": "audio/mp4",
|
|
66
|
+
"audio/m4a": "audio/mp4",
|
|
67
|
+
"audio/wave": "audio/wav",
|
|
68
|
+
"audio/vnd.wave": "audio/wav",
|
|
69
|
+
"audio/x-aac": "audio/aac",
|
|
70
|
+
"image/jpg": "image/jpeg",
|
|
71
|
+
"video/x-quicktime": "video/quicktime",
|
|
72
|
+
"video/mov": "video/quicktime",
|
|
73
|
+
"video/x-m4v": "video/mp4",
|
|
74
|
+
"video/mkv": "video/x-matroska"
|
|
75
|
+
};
|
|
76
|
+
var TYPE_BY_EXTENSION = Object.entries(EXT).reduce(
|
|
77
|
+
(acc, [contentType, ext]) => {
|
|
78
|
+
if (!(ext in acc)) acc[ext] = contentType;
|
|
79
|
+
return acc;
|
|
80
|
+
},
|
|
81
|
+
{}
|
|
82
|
+
);
|
|
83
|
+
TYPE_BY_EXTENSION["jpeg"] = "image/jpeg";
|
|
84
|
+
TYPE_BY_EXTENSION["m4a"] = "audio/mp4";
|
|
85
|
+
var POOL_EXTENSIONS = Object.entries(TYPE_BY_EXTENSION).filter(([, contentType]) => mediaKind(contentType) !== "opaque").map(([ext]) => ext);
|
|
86
|
+
function contentTypeForUpload(filename, claimed) {
|
|
87
|
+
const ct = (claimed ?? "").trim().toLowerCase();
|
|
88
|
+
const allowed = ALLOWED_CONTENT_TYPES;
|
|
89
|
+
if (ct && allowed.includes(ct)) return ct;
|
|
90
|
+
if (ct && CONTENT_TYPE_ALIASES[ct]) return CONTENT_TYPE_ALIASES[ct];
|
|
91
|
+
const ext = filename.toLowerCase().split(".").pop() ?? "";
|
|
92
|
+
const byExt = TYPE_BY_EXTENSION[ext];
|
|
93
|
+
if (byExt) return byExt;
|
|
94
|
+
return ct;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/multipartUpload.ts
|
|
98
|
+
var CHUNK_SIZE = 10 * 1024 * 1024;
|
|
99
|
+
async function reasonFrom(res) {
|
|
100
|
+
try {
|
|
101
|
+
const text = await res.text();
|
|
102
|
+
if (!text) return `HTTP ${res.status}`;
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(text);
|
|
105
|
+
if (typeof parsed.error === "string" && parsed.error) return `${parsed.error} (HTTP ${res.status})`;
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
return `${text.slice(0, 200)} (HTTP ${res.status})`;
|
|
109
|
+
} catch {
|
|
110
|
+
return `HTTP ${res.status}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function multipartUpload(opts) {
|
|
114
|
+
const { file, signingUrl, token, metadata, onProgress } = opts;
|
|
115
|
+
const baseUrl = signingUrl.replace(/\/$/, "");
|
|
116
|
+
const headers = { "uppy-auth-token": token, "Content-Type": "application/json" };
|
|
117
|
+
const createRes = await fetch(`${baseUrl}/s3/multipart`, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers,
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
filename: file.name,
|
|
122
|
+
// NOT `file.type || "video/mp4"`. That fallback relabelled every untyped
|
|
123
|
+
// file as video, so an MP3 with no MIME uploaded as `video/mp4` and was
|
|
124
|
+
// routed down the video transcode path by `mediaKind()` — a wrong
|
|
125
|
+
// pipeline instead of an honest refusal. See `contentTypeForUpload`.
|
|
126
|
+
type: contentTypeForUpload(file.name, file.type),
|
|
127
|
+
metadata: { uploadToken: token, ...metadata }
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
if (!createRes.ok) throw new Error(`Upload init failed for ${file.name}: ${await reasonFrom(createRes)}`);
|
|
131
|
+
const { uploadId } = await createRes.json();
|
|
132
|
+
const totalParts = Math.ceil(file.size / CHUNK_SIZE);
|
|
133
|
+
const parts = [];
|
|
134
|
+
for (let i = 0; i < totalParts; i++) {
|
|
135
|
+
const start = i * CHUNK_SIZE;
|
|
136
|
+
const end = Math.min(start + CHUNK_SIZE, file.size);
|
|
137
|
+
const chunk = file.slice(start, end);
|
|
138
|
+
const partNumber = i + 1;
|
|
139
|
+
const urlRes = await fetch(`${baseUrl}/s3/multipart/${uploadId}/${partNumber}`, {
|
|
140
|
+
headers: { "uppy-auth-token": token }
|
|
141
|
+
});
|
|
142
|
+
if (!urlRes.ok) throw new Error(`Presign failed for ${file.name} part ${partNumber}: ${await reasonFrom(urlRes)}`);
|
|
143
|
+
const { url } = await urlRes.json();
|
|
144
|
+
const putRes = await fetch(url, { method: "PUT", body: chunk });
|
|
145
|
+
if (!putRes.ok) throw new Error(`Part ${partNumber} of ${file.name} failed: ${await reasonFrom(putRes)}`);
|
|
146
|
+
parts.push({ PartNumber: partNumber, ETag: putRes.headers.get("ETag") ?? "" });
|
|
147
|
+
onProgress?.(end / file.size);
|
|
148
|
+
}
|
|
149
|
+
const completeRes = await fetch(`${baseUrl}/s3/multipart/${uploadId}/complete`, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers,
|
|
152
|
+
body: JSON.stringify({ parts })
|
|
153
|
+
});
|
|
154
|
+
if (!completeRes.ok) throw new Error(`Complete failed for ${file.name}: ${await reasonFrom(completeRes)}`);
|
|
155
|
+
return await completeRes.json();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
__publicField,
|
|
160
|
+
multipartUpload
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=chunk-XKO6ZSWI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../data-models/media-model/src/media-kind.ts","../src/multipartUpload.ts"],"sourcesContent":["/** `opaque` is `asset` renamed (spec §5): \"asset\" was the word that made two\n * different concepts — storage furniture and a media classification — look\n * like one. `opaque` states the contract: no pipeline, not placeable. */\nexport type MediaKind = \"video\" | \"audio\" | \"image\" | \"opaque\";\n\nexport const IMAGE_CONTENT_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\"] as const;\n\n/** Closed allowlist — the complete set of accepted content-types (the acceptance\n * gate). mediaKind() routes each: video/audio/still-image do real work;\n * fonts/json/svg/gif route to the opaque path. Unknown bytes (not here)\n * are rejected at the edge. */\nexport const ALLOWED_CONTENT_TYPES = [\n \"video/mp4\", \"video/quicktime\", \"video/x-matroska\", \"video/webm\",\n \"audio/mpeg\", \"audio/wav\", \"audio/x-wav\", \"audio/mp4\", \"audio/aac\",\n \"image/png\", \"image/jpeg\", \"image/webp\",\n \"image/gif\", \"image/svg+xml\", \"application/json\",\n \"font/ttf\", \"font/otf\", \"font/woff\", \"font/woff2\",\n] as const;\n\nexport type AllowedContentType = (typeof ALLOWED_CONTENT_TYPES)[number];\n\nexport function mediaKind(contentType: string): MediaKind {\n const ct = (contentType || \"\").toLowerCase();\n if (ct.startsWith(\"video/\")) return \"video\";\n if (ct.startsWith(\"audio/\")) return \"audio\";\n if ((IMAGE_CONTENT_TYPES as readonly string[]).includes(ct)) return \"image\";\n return \"opaque\";\n}\n\nconst EXT: Record<string, string> = {\n \"video/mp4\": \"mp4\", \"audio/mp4\": \"mp4\", \"video/quicktime\": \"mov\",\n \"video/x-matroska\": \"mkv\", \"video/webm\": \"webm\", \"video/x-msvideo\": \"avi\",\n \"audio/mpeg\": \"mp3\", \"audio/wav\": \"wav\", \"audio/x-wav\": \"wav\", \"audio/aac\": \"aac\",\n \"audio/ogg\": \"ogg\", \"audio/flac\": \"flac\",\n \"image/png\": \"png\", \"image/jpeg\": \"jpg\", \"image/webp\": \"webp\",\n \"font/ttf\": \"ttf\", \"font/otf\": \"otf\", \"font/woff\": \"woff\", \"font/woff2\": \"woff2\",\n \"application/json\": \"json\", \"image/svg+xml\": \"svg\", \"image/gif\": \"gif\",\n};\nexport function extensionForContentType(contentType: string): string {\n return EXT[(contentType || \"\").toLowerCase()] ?? \"bin\";\n}\n\n/**\n * MIME ALIASES THE PLATFORM EMITS FOR TYPES WE ALREADY ACCEPT.\n *\n * `ALLOWED_CONTENT_TYPES` is a CLOSED gate keyed on an exact string, and the\n * same bytes get different strings depending on who describes them: WebKit says\n * `audio/mp3` where the registry says `audio/mpeg`, Apple says `audio/x-m4a`\n * where the registry says `audio/mp4`. Rejecting those is not the gate doing\n * its job — it is the gate mistaking a spelling for a format, and the user gets\n * \"Unsupported file type\" for a plain MP3.\n *\n * Only aliases of types ALREADY on the allowlist belong here. This must not\n * become a way to smuggle in a format the pipeline cannot decode.\n */\nconst CONTENT_TYPE_ALIASES: Record<string, string> = {\n \"audio/mp3\": \"audio/mpeg\",\n \"audio/x-mp3\": \"audio/mpeg\",\n \"audio/x-mpeg\": \"audio/mpeg\",\n \"audio/mpeg3\": \"audio/mpeg\",\n \"audio/x-mpeg-3\": \"audio/mpeg\",\n \"audio/x-m4a\": \"audio/mp4\",\n \"audio/m4a\": \"audio/mp4\",\n \"audio/wave\": \"audio/wav\",\n \"audio/vnd.wave\": \"audio/wav\",\n \"audio/x-aac\": \"audio/aac\",\n \"image/jpg\": \"image/jpeg\",\n \"video/x-quicktime\": \"video/quicktime\",\n \"video/mov\": \"video/quicktime\",\n \"video/x-m4v\": \"video/mp4\",\n \"video/mkv\": \"video/x-matroska\",\n};\n\n/** Extension → canonical content-type, derived from the EXT map so the two\n * cannot drift. Built once. */\nconst TYPE_BY_EXTENSION: Record<string, string> = Object.entries(EXT).reduce<Record<string, string>>(\n (acc, [contentType, ext]) => {\n // First writer wins, so `audio/mp4` does not steal `mp4` from `video/mp4`.\n if (!(ext in acc)) acc[ext] = contentType;\n return acc;\n },\n {},\n);\n// Spellings EXT cannot carry (it is contentType→ext, one ext per type): the\n// long form of jpg, and m4a — whose canonical type audio/mp4 lost the \"mp4\"\n// slot to video above.\nTYPE_BY_EXTENSION[\"jpeg\"] = \"image/jpeg\";\nTYPE_BY_EXTENSION[\"m4a\"] = \"audio/mp4\";\n\n/** What a file IS, from nothing but its name — the ONE derivation the import\n * door, the project-file parse gate, and migration 020's SQL all mirror. */\nexport function mediaKindForFilename(filename: string): MediaKind {\n return mediaKind(contentTypeForUpload(filename, null));\n}\n\n/** Every extension whose derived kind can occupy time. The import door's\n * accept-list IS this list — derived, so the door and the authority cannot\n * disagree (the disagreement was a thrown import for `.jpeg`/`.m4a`). */\nexport const POOL_EXTENSIONS: readonly string[] = Object.entries(TYPE_BY_EXTENSION)\n .filter(([, contentType]) => mediaKind(contentType) !== \"opaque\")\n .map(([ext]) => ext);\n\n/**\n * The content-type to UPLOAD a file as, given what the platform claimed and\n * what the file is called.\n *\n * WHY THIS EXISTS. The upload client used to send `file.type || \"video/mp4\"`.\n * Both halves are wrong: the alias case rejects real MP3s (see above), and the\n * fallback silently relabels ANY untyped file as video — so an MP3 dragged in\n * without a MIME is accepted and then routed down the VIDEO transcode path by\n * `mediaKind()`, which is a wrong pipeline rather than an honest refusal.\n *\n * Order: an already-allowed type wins, then a known alias, then the extension.\n * If none of those resolve, the ORIGINAL string is returned unchanged — never a\n * guess. The closed gate then refuses it and names the type the user actually\n * has, which is the error message they need.\n */\nexport function contentTypeForUpload(filename: string, claimed?: string | null): string {\n const ct = (claimed ?? \"\").trim().toLowerCase();\n const allowed = ALLOWED_CONTENT_TYPES as readonly string[];\n\n if (ct && allowed.includes(ct)) return ct;\n if (ct && CONTENT_TYPE_ALIASES[ct]) return CONTENT_TYPE_ALIASES[ct];\n\n const ext = filename.toLowerCase().split(\".\").pop() ?? \"\";\n const byExt = TYPE_BY_EXTENSION[ext];\n if (byExt) return byExt;\n\n return ct;\n}\n","/**\n * S3 multipart upload via the upload-signing CF Worker.\n * Shared by FileUpload component and adapter.ts.\n *\n * EVERY FAILURE HERE CARRIES THE SERVER'S REASON. These throws used to be\n * `Upload init failed: ${status}` — status only. The worker answers a rejected\n * create with `{\"error\":\"Unsupported file type: audio/mp3\"}`, i.e. it says\n * exactly what is wrong and which type it saw, and the client discarded it. A\n * user then sees \"failed\" with no cause, and neither the app log nor any\n * server-side log has the string either (this module has no logger and the\n * worker returns 400 rather than throwing). On 2026-08-18 that cost a long\n * diagnosis of a single stuck MP3. `reasonFrom` puts the body back.\n */\n// SUBPATH, NOT THE BARREL. `@cueframe/media-model`'s root re-exports `./api`,\n// which pulls zod and the Convex validators — none of which this BROWSER bundle\n// needs. The `./media-kind` entry is the pure classifier, zero imports; take it\n// alone. (Until 2026-08-25 this classifier lived in `@cueframe/shared`, whose\n// barrel dragged `@sentry/node-core` in and broke the build on `\"subscribe\" is\n// not exported by \"__vite-browser-external\"` — a sharper version of the same\n// rule. It moved so no public package reaches into the private `shared`.)\nimport { contentTypeForUpload } from \"@cueframe/media-model/media-kind\";\n\nconst CHUNK_SIZE = 10 * 1024 * 1024; // 10MB parts\n\n/**\n * The server's own words, when it has any. The worker replies with\n * `{error: string}`; anything else (an HTML error page, an empty body) falls\n * back to the status, which is still better than nothing.\n */\nasync function reasonFrom(res: Response): Promise<string> {\n try {\n const text = await res.text();\n if (!text) return `HTTP ${res.status}`;\n try {\n const parsed = JSON.parse(text) as { error?: unknown };\n if (typeof parsed.error === \"string\" && parsed.error) return `${parsed.error} (HTTP ${res.status})`;\n } catch {\n // Not JSON — fall through to the raw text.\n }\n return `${text.slice(0, 200)} (HTTP ${res.status})`;\n } catch {\n return `HTTP ${res.status}`;\n }\n}\n\nexport interface MultipartUploadOptions {\n file: File;\n signingUrl: string;\n token: string;\n metadata?: Record<string, string>;\n onProgress?: (fraction: number) => void;\n}\n\nexport interface MultipartUploadResult {\n location: string;\n mediaItemId: string;\n}\n\nexport async function multipartUpload(opts: MultipartUploadOptions): Promise<MultipartUploadResult> {\n const { file, signingUrl, token, metadata, onProgress } = opts;\n const baseUrl = signingUrl.replace(/\\/$/, \"\");\n const headers = { \"uppy-auth-token\": token, \"Content-Type\": \"application/json\" };\n\n // 1. Create multipart upload\n const createRes = await fetch(`${baseUrl}/s3/multipart`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n filename: file.name,\n // NOT `file.type || \"video/mp4\"`. That fallback relabelled every untyped\n // file as video, so an MP3 with no MIME uploaded as `video/mp4` and was\n // routed down the video transcode path by `mediaKind()` — a wrong\n // pipeline instead of an honest refusal. See `contentTypeForUpload`.\n type: contentTypeForUpload(file.name, file.type),\n metadata: { uploadToken: token, ...metadata },\n }),\n });\n if (!createRes.ok) throw new Error(`Upload init failed for ${file.name}: ${await reasonFrom(createRes)}`);\n const { uploadId } = (await createRes.json()) as { uploadId: string; key: string };\n\n // 2. Upload parts sequentially\n const totalParts = Math.ceil(file.size / CHUNK_SIZE);\n const parts: Array<{ PartNumber: number; ETag: string }> = [];\n\n for (let i = 0; i < totalParts; i++) {\n const start = i * CHUNK_SIZE;\n const end = Math.min(start + CHUNK_SIZE, file.size);\n const chunk = file.slice(start, end);\n const partNumber = i + 1;\n\n // Get presigned URL\n const urlRes = await fetch(`${baseUrl}/s3/multipart/${uploadId}/${partNumber}`, {\n headers: { \"uppy-auth-token\": token },\n });\n if (!urlRes.ok) throw new Error(`Presign failed for ${file.name} part ${partNumber}: ${await reasonFrom(urlRes)}`);\n const { url } = (await urlRes.json()) as { url: string };\n\n // PUT to presigned URL\n const putRes = await fetch(url, { method: \"PUT\", body: chunk });\n if (!putRes.ok) throw new Error(`Part ${partNumber} of ${file.name} failed: ${await reasonFrom(putRes)}`);\n parts.push({ PartNumber: partNumber, ETag: putRes.headers.get(\"ETag\") ?? \"\" });\n\n onProgress?.(end / file.size);\n }\n\n // 3. Complete multipart upload\n const completeRes = await fetch(`${baseUrl}/s3/multipart/${uploadId}/complete`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ parts }),\n });\n if (!completeRes.ok) throw new Error(`Complete failed for ${file.name}: ${await reasonFrom(completeRes)}`);\n\n return (await completeRes.json()) as MultipartUploadResult;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for cloud endpoint resolution.
|
|
3
|
+
*
|
|
4
|
+
* One input (convexUrl) → deterministic output (all cloud endpoints).
|
|
5
|
+
* No fallback chains, no env var reading, no defaults.
|
|
6
|
+
*/
|
|
7
|
+
export type CloudEnvironment = 'dev' | 'staging' | 'production';
|
|
8
|
+
export interface CloudEndpoints {
|
|
9
|
+
convexUrl: string;
|
|
10
|
+
uploadSigningUrl: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const ENVIRONMENTS: Record<CloudEnvironment, CloudEndpoints>;
|
|
13
|
+
/** Detect environment from a Convex deployment URL.
|
|
14
|
+
*
|
|
15
|
+
* Convex exposes two domains per deployment: queries (`data*`) and HTTP
|
|
16
|
+
* actions (`api*`). Callers pass either form depending on which client
|
|
17
|
+
* they own, so detection has to recognize both:
|
|
18
|
+
* prod → api.cueframe.ai | data.cueframe.ai
|
|
19
|
+
* staging → api-dev.cueframe.ai | data-dev.cueframe.ai
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectEnvironment(convexUrl?: string): CloudEnvironment;
|
|
22
|
+
/** Resolve all cloud endpoints from a Convex URL. */
|
|
23
|
+
export declare function getEndpoints(convexUrl: string): CloudEndpoints;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { ImportProvider, useImport, useImportIfAvailable } from './ImportProvider.js';
|
|
2
|
+
export type { ImportProviderProps } from './ImportProvider.js';
|
|
3
|
+
export { cloudImportService } from './CloudImportService.js';
|
|
4
|
+
export type { CloudUploadResult } from './CloudImportService.js';
|
|
5
|
+
export { ENVIRONMENTS, detectEnvironment, getEndpoints } from './cloudConfig.js';
|
|
6
|
+
export type { CloudEnvironment, CloudEndpoints } from './cloudConfig.js';
|
|
7
|
+
export { multipartUpload } from './multipartUpload.js';
|
|
8
|
+
export type { MultipartUploadOptions, MultipartUploadResult } from './multipartUpload.js';
|
|
9
|
+
export { useDropzone, useUppyState, useUppyEvent, } from '@uppy/react';
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField,
|
|
3
|
+
multipartUpload
|
|
4
|
+
} from "./chunk-XKO6ZSWI.js";
|
|
5
|
+
|
|
6
|
+
// src/ImportProvider.tsx
|
|
7
|
+
import { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
|
|
8
|
+
import { UppyContextProvider } from "@uppy/react";
|
|
9
|
+
|
|
10
|
+
// src/CloudImportService.ts
|
|
11
|
+
import Uppy from "@uppy/core";
|
|
12
|
+
import AwsS3 from "@uppy/aws-s3";
|
|
13
|
+
|
|
14
|
+
// src/MediaItemPreprocessor.ts
|
|
15
|
+
import { BasePlugin } from "@uppy/core";
|
|
16
|
+
var _MediaItemPreprocessor = class _MediaItemPreprocessor extends BasePlugin {
|
|
17
|
+
constructor(uppy, opts) {
|
|
18
|
+
super(uppy, opts ?? {});
|
|
19
|
+
__publicField(this, "createFn", null);
|
|
20
|
+
__publicField(this, "preprocessBound");
|
|
21
|
+
this.id = _MediaItemPreprocessor.pluginId;
|
|
22
|
+
this.type = "preprocessor";
|
|
23
|
+
this.preprocessBound = this.preprocess.bind(this);
|
|
24
|
+
}
|
|
25
|
+
/** Inject the creation function (called from ImportProvider). */
|
|
26
|
+
setCreateFn(fn) {
|
|
27
|
+
this.createFn = fn;
|
|
28
|
+
}
|
|
29
|
+
install() {
|
|
30
|
+
this.uppy.addPreProcessor(this.preprocessBound);
|
|
31
|
+
}
|
|
32
|
+
uninstall() {
|
|
33
|
+
this.uppy.removePreProcessor(this.preprocessBound);
|
|
34
|
+
}
|
|
35
|
+
async preprocess(fileIDs) {
|
|
36
|
+
if (!this.createFn) return;
|
|
37
|
+
for (const fileID of fileIDs) {
|
|
38
|
+
const file = this.uppy.getFile(fileID);
|
|
39
|
+
if (!file) continue;
|
|
40
|
+
if (file.meta.convexMediaItemId) continue;
|
|
41
|
+
this.uppy.emit("preprocess-progress", file, {
|
|
42
|
+
mode: "indeterminate",
|
|
43
|
+
message: "Preparing upload\u2026"
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
const mediaItemId = await this.createFn({
|
|
47
|
+
name: file.name ?? "unknown",
|
|
48
|
+
size: file.size ?? 0,
|
|
49
|
+
mimeType: file.type ?? "video/mp4"
|
|
50
|
+
});
|
|
51
|
+
this.uppy.setFileMeta(fileID, { convexMediaItemId: mediaItemId });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
this.uppy.log(`[MediaItemPreprocessor] Failed for ${fileID}: ${err}`, "error");
|
|
54
|
+
}
|
|
55
|
+
this.uppy.emit("preprocess-complete", file);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
__publicField(_MediaItemPreprocessor, "pluginId", "MediaItemPreprocessor");
|
|
60
|
+
var MediaItemPreprocessor = _MediaItemPreprocessor;
|
|
61
|
+
|
|
62
|
+
// src/CloudImportService.ts
|
|
63
|
+
var CloudImportService = class {
|
|
64
|
+
constructor() {
|
|
65
|
+
__publicField(this, "uppy", null);
|
|
66
|
+
__publicField(this, "config", null);
|
|
67
|
+
}
|
|
68
|
+
/** Lazy-init: create Uppy + plugins on first use. */
|
|
69
|
+
init(config) {
|
|
70
|
+
if (this.uppy) return this.uppy;
|
|
71
|
+
this.config = config;
|
|
72
|
+
const signingUrl = this.config.uploadSigningUrl.replace(/\/$/, "");
|
|
73
|
+
this.uppy = new Uppy({
|
|
74
|
+
id: "cloud-import",
|
|
75
|
+
restrictions: {
|
|
76
|
+
allowedFileTypes: ["video/*", "audio/*"]
|
|
77
|
+
}
|
|
78
|
+
}).use(AwsS3, {
|
|
79
|
+
// Upload-signing Worker implements the S3 multipart endpoints at /s3/multipart/*
|
|
80
|
+
endpoint: signingUrl,
|
|
81
|
+
shouldUseMultipart: true,
|
|
82
|
+
// Cap concurrent multipart part uploads. Unbounded concurrency
|
|
83
|
+
// saturates the browser network stack and the signing Worker on
|
|
84
|
+
// large files; 3 keeps throughput high without melting either.
|
|
85
|
+
limit: 3,
|
|
86
|
+
// Exponential-ish backoff for failed part/signing requests. Uppy
|
|
87
|
+
// retries the same operation on each delay — keeps transient
|
|
88
|
+
// network blips from failing an entire upload.
|
|
89
|
+
retryDelays: [1e3, 3e3, 5e3, 1e4]
|
|
90
|
+
}).use(MediaItemPreprocessor);
|
|
91
|
+
return this.uppy;
|
|
92
|
+
}
|
|
93
|
+
/** Get the Uppy instance (must call init() first). */
|
|
94
|
+
getUppy() {
|
|
95
|
+
if (!this.uppy) throw new Error("CloudImportService not initialized \u2014 call init() first");
|
|
96
|
+
return this.uppy;
|
|
97
|
+
}
|
|
98
|
+
/** Get the MediaItemPreprocessor plugin. */
|
|
99
|
+
getPreprocessor() {
|
|
100
|
+
return this.getUppy().getPlugin(MediaItemPreprocessor.pluginId);
|
|
101
|
+
}
|
|
102
|
+
/** Update upload metadata (uploadToken, userId, projectId). */
|
|
103
|
+
setMeta(meta) {
|
|
104
|
+
this.uppy?.setMeta(meta);
|
|
105
|
+
if (meta.uploadToken) {
|
|
106
|
+
const plugin = this.uppy?.getPlugin("AwsS3Multipart");
|
|
107
|
+
if (plugin) {
|
|
108
|
+
plugin.setOptions({ headers: { "uppy-auth-token": meta.uploadToken } });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var cloudImportService = new CloudImportService();
|
|
114
|
+
|
|
115
|
+
// src/ImportProvider.tsx
|
|
116
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
117
|
+
var ImportContext = createContext(null);
|
|
118
|
+
function ImportProvider({
|
|
119
|
+
children,
|
|
120
|
+
available,
|
|
121
|
+
dialogSlot,
|
|
122
|
+
uploadSigningUrl,
|
|
123
|
+
getUploadMeta,
|
|
124
|
+
createMediaItem,
|
|
125
|
+
onUploadComplete,
|
|
126
|
+
onError
|
|
127
|
+
}) {
|
|
128
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
129
|
+
const onCompleteRef = useRef(onUploadComplete);
|
|
130
|
+
const onErrorRef = useRef(onError);
|
|
131
|
+
onCompleteRef.current = onUploadComplete;
|
|
132
|
+
onErrorRef.current = onError;
|
|
133
|
+
const [uppy] = useState(() => cloudImportService.init({ uploadSigningUrl }));
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const handleComplete = (result) => {
|
|
136
|
+
const successful = result?.successful ?? [];
|
|
137
|
+
if (successful.length > 0 && onCompleteRef.current) {
|
|
138
|
+
onCompleteRef.current(
|
|
139
|
+
successful.map((file) => ({
|
|
140
|
+
uploadURL: file.response?.uploadURL ?? "",
|
|
141
|
+
fileName: file.name ?? "unknown",
|
|
142
|
+
fileSize: file.size ?? 0
|
|
143
|
+
}))
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const handleError = (error) => {
|
|
148
|
+
onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
|
|
149
|
+
};
|
|
150
|
+
uppy.on("complete", handleComplete);
|
|
151
|
+
uppy.on("error", handleError);
|
|
152
|
+
return () => {
|
|
153
|
+
uppy.off("complete", handleComplete);
|
|
154
|
+
uppy.off("error", handleError);
|
|
155
|
+
};
|
|
156
|
+
}, [uppy]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
try {
|
|
159
|
+
const preprocessor = cloudImportService.getPreprocessor();
|
|
160
|
+
preprocessor.setCreateFn(createMediaItem ?? null);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}, [createMediaItem]);
|
|
164
|
+
const openModal = useCallback(async () => {
|
|
165
|
+
if (getUploadMeta) {
|
|
166
|
+
try {
|
|
167
|
+
const meta = await getUploadMeta();
|
|
168
|
+
cloudImportService.setMeta(meta);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error("[Import] Failed to get upload metadata:", err);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
setIsOpen(true);
|
|
175
|
+
}, [getUploadMeta]);
|
|
176
|
+
const closeModal = useCallback(() => {
|
|
177
|
+
setIsOpen(false);
|
|
178
|
+
}, []);
|
|
179
|
+
return /* @__PURE__ */ jsx(ImportContext, { value: { openModal, closeModal, isOpen, uppy, available }, children: /* @__PURE__ */ jsxs(UppyContextProvider, { uppy, children: [
|
|
180
|
+
children,
|
|
181
|
+
dialogSlot
|
|
182
|
+
] }) });
|
|
183
|
+
}
|
|
184
|
+
function useImport() {
|
|
185
|
+
const ctx = useContext(ImportContext);
|
|
186
|
+
if (!ctx) throw new Error("useImport must be used within <ImportProvider>");
|
|
187
|
+
return ctx;
|
|
188
|
+
}
|
|
189
|
+
function useImportIfAvailable() {
|
|
190
|
+
const ctx = useContext(ImportContext);
|
|
191
|
+
return ctx?.available ? ctx : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/cloudConfig.ts
|
|
195
|
+
var ENVIRONMENTS = {
|
|
196
|
+
dev: {
|
|
197
|
+
convexUrl: "https://api-dev.cueframe.ai",
|
|
198
|
+
uploadSigningUrl: "http://localhost:8787"
|
|
199
|
+
},
|
|
200
|
+
staging: {
|
|
201
|
+
convexUrl: "https://api-dev.cueframe.ai",
|
|
202
|
+
uploadSigningUrl: "https://upload-signing.cueframelabs.workers.dev"
|
|
203
|
+
},
|
|
204
|
+
production: {
|
|
205
|
+
convexUrl: "https://api.cueframe.ai",
|
|
206
|
+
uploadSigningUrl: "https://upload.cueframe.ai"
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
function detectEnvironment(convexUrl) {
|
|
210
|
+
if (!convexUrl) return "dev";
|
|
211
|
+
if (/\b(api-dev|data-dev)\.cueframe\.ai/.test(convexUrl)) return "staging";
|
|
212
|
+
if (/\b(api|data)\.cueframe\.ai/.test(convexUrl)) return "production";
|
|
213
|
+
if (/\boceanic-dogfish-357\.convex\.(cloud|site)/.test(convexUrl)) return "staging";
|
|
214
|
+
if (/\bpolished-hawk-150\.convex\.(cloud|site)/.test(convexUrl)) return "production";
|
|
215
|
+
return "dev";
|
|
216
|
+
}
|
|
217
|
+
function getEndpoints(convexUrl) {
|
|
218
|
+
const env = detectEnvironment(convexUrl);
|
|
219
|
+
return ENVIRONMENTS[env];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/index.ts
|
|
223
|
+
import {
|
|
224
|
+
useDropzone,
|
|
225
|
+
useUppyState,
|
|
226
|
+
useUppyEvent
|
|
227
|
+
} from "@uppy/react";
|
|
228
|
+
export {
|
|
229
|
+
ENVIRONMENTS,
|
|
230
|
+
ImportProvider,
|
|
231
|
+
cloudImportService,
|
|
232
|
+
detectEnvironment,
|
|
233
|
+
getEndpoints,
|
|
234
|
+
multipartUpload,
|
|
235
|
+
useDropzone,
|
|
236
|
+
useImport,
|
|
237
|
+
useImportIfAvailable,
|
|
238
|
+
useUppyEvent,
|
|
239
|
+
useUppyState
|
|
240
|
+
};
|
|
241
|
+
//# sourceMappingURL=index.js.map
|