@volter/editor-game 0.5.65
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/LICENSE +661 -0
- package/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Generations gallery SHOWS about a job — derived entirely from the
|
|
3
|
+
* durable `GenerationJob` ledger, never from a second store or a provider poll.
|
|
4
|
+
*
|
|
5
|
+
* The gallery is asset-first: a card's subject is the file the job produced,
|
|
6
|
+
* so everything here answers "which bytes, of what kind, under what name",
|
|
7
|
+
* plus the small amount of text (state, model, charge) that frames them.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ASSET_ROOTS, assetRootServingUrl } from '@volter/editor-core/asset-workflow/project-asset-roots';
|
|
11
|
+
import type { GenerationBilling, GenerationJob } from '@volter/editor-sdk/generations';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The state a card presents, which is NOT `job.status` alone.
|
|
15
|
+
*
|
|
16
|
+
* `succeeded` splits four ways because the server reconciler accepts a
|
|
17
|
+
* finished job's outputs AUTOMATICALLY — there is no manual add-to-project
|
|
18
|
+
* gate anywhere in this surface. Until `acceptedAt` lands, a succeeded job
|
|
19
|
+
* that still carries an `accept` operation is mid-SAVE; if that save recorded
|
|
20
|
+
* an error it is `save-failed` (the server stops after its own bounded
|
|
21
|
+
* attempts, so the card must stop claiming progress and offer the retry); and
|
|
22
|
+
* a succeeded job that never had an `accept` produced nothing to hold.
|
|
23
|
+
*
|
|
24
|
+
* `save-failed` is deliberately its own value rather than folded into
|
|
25
|
+
* `failed`: both read as a failure to the eye (same tone, same border), but
|
|
26
|
+
* only one of them has a save to retry, and only the other means the provider
|
|
27
|
+
* itself failed.
|
|
28
|
+
*/
|
|
29
|
+
export type GenerationCardState =
|
|
30
|
+
| 'queued'
|
|
31
|
+
| 'running'
|
|
32
|
+
| 'saving'
|
|
33
|
+
| 'save-failed'
|
|
34
|
+
| 'ready'
|
|
35
|
+
| 'no-asset'
|
|
36
|
+
| 'failed'
|
|
37
|
+
| 'cancelled';
|
|
38
|
+
|
|
39
|
+
export function generationCardState(job: GenerationJob): GenerationCardState {
|
|
40
|
+
switch (job.status) {
|
|
41
|
+
case 'queued':
|
|
42
|
+
return 'queued';
|
|
43
|
+
case 'running':
|
|
44
|
+
return 'running';
|
|
45
|
+
case 'failed':
|
|
46
|
+
return 'failed';
|
|
47
|
+
case 'cancelled':
|
|
48
|
+
return 'cancelled';
|
|
49
|
+
default:
|
|
50
|
+
if (job.acceptedAt) return 'ready';
|
|
51
|
+
if (!job.accept) return 'no-asset';
|
|
52
|
+
return job.pollError ? 'save-failed' : 'saving';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether forgetting the job now can lose nothing in flight — the one rule
|
|
58
|
+
* both the gallery card and the detail document offer "Remove" by.
|
|
59
|
+
*/
|
|
60
|
+
export function generationRemovable(job: GenerationJob): boolean {
|
|
61
|
+
const state = generationCardState(job);
|
|
62
|
+
return state !== 'queued' && state !== 'running' && state !== 'saving';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Whether the job's own accept operation is still worth running by hand. */
|
|
66
|
+
export function generationSaveRetryable(job: GenerationJob, actionError: string | null): boolean {
|
|
67
|
+
return (
|
|
68
|
+
job.status === 'succeeded' &&
|
|
69
|
+
Boolean(job.accept) &&
|
|
70
|
+
!job.acceptedAt &&
|
|
71
|
+
Boolean(job.pollError ?? actionError)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Short, human state word for the card's status chip. */
|
|
76
|
+
export const GENERATION_STATE_LABEL: Record<GenerationCardState, string> = {
|
|
77
|
+
queued: 'Queued',
|
|
78
|
+
running: 'Running',
|
|
79
|
+
saving: 'Saving',
|
|
80
|
+
'save-failed': "Couldn't save",
|
|
81
|
+
ready: 'In project',
|
|
82
|
+
'no-asset': 'No file',
|
|
83
|
+
failed: 'Failed',
|
|
84
|
+
cancelled: 'Cancelled',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** How a produced file can be shown. `file` means "no honest preview exists". */
|
|
88
|
+
export type GenerationPreviewKind = 'image' | 'video' | 'audio' | 'model' | 'file';
|
|
89
|
+
|
|
90
|
+
const PREVIEW_KINDS: ReadonlyMap<string, GenerationPreviewKind> = new Map([
|
|
91
|
+
...(['png', 'jpg', 'jpeg', 'webp', 'gif', 'avif', 'svg', 'bmp'] as const).map(
|
|
92
|
+
(extension) => [extension, 'image'] as const,
|
|
93
|
+
),
|
|
94
|
+
// Provider video output is what `<video controls>` decodes natively; an
|
|
95
|
+
// extension outside this set gets the file row, never a guessed player.
|
|
96
|
+
...(['mp4', 'webm', 'ogv', 'mov', 'm4v'] as const).map(
|
|
97
|
+
(extension) => [extension, 'video'] as const,
|
|
98
|
+
),
|
|
99
|
+
...(['mp3', 'wav', 'ogg', 'oga', 'm4a', 'flac', 'aac'] as const).map(
|
|
100
|
+
(extension) => [extension, 'audio'] as const,
|
|
101
|
+
),
|
|
102
|
+
...(['glb', 'gltf', 'fbx', 'obj', 'stl', 'ply', 'dae', '3ds', 'usdz', 'spz'] as const).map(
|
|
103
|
+
(extension) => [extension, 'model'] as const,
|
|
104
|
+
),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
export interface GenerationOutput {
|
|
108
|
+
/** Project-relative path exactly as the ledger recorded it. */
|
|
109
|
+
readonly path: string;
|
|
110
|
+
/** File name, for the card's name and the preview's alt text. */
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly kind: GenerationPreviewKind;
|
|
113
|
+
/**
|
|
114
|
+
* Root-relative URL the dev server answers, or null for an output that does
|
|
115
|
+
* does not live under either project asset root.
|
|
116
|
+
*/
|
|
117
|
+
readonly url: string | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function generationOutputExtension(path: string): string {
|
|
121
|
+
const name = path.slice(path.lastIndexOf('/') + 1).toLowerCase();
|
|
122
|
+
const dot = name.lastIndexOf('.');
|
|
123
|
+
return dot > 0 ? name.slice(dot + 1) : '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function generationOutputs(job: GenerationJob): readonly GenerationOutput[] {
|
|
127
|
+
return (job.outputPaths ?? []).map((path) => {
|
|
128
|
+
const root = ASSET_ROOTS.find((root) => path.startsWith(`${root}/`));
|
|
129
|
+
const url = root ? assetRootServingUrl(root, path.slice(root.length + 1)) : null;
|
|
130
|
+
return {
|
|
131
|
+
path,
|
|
132
|
+
name: path.slice(path.lastIndexOf('/') + 1),
|
|
133
|
+
kind: PREVIEW_KINDS.get(generationOutputExtension(path)) ?? 'file',
|
|
134
|
+
url,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function shorten(text: string, max: number): string {
|
|
140
|
+
const clean = text.trim();
|
|
141
|
+
return clean.length <= max ? clean : `${clean.slice(0, max - 1).trimEnd()}…`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A concise card name.
|
|
146
|
+
*
|
|
147
|
+
* A job's `label` is the submitted PROMPT, already truncated by the provider
|
|
148
|
+
* extension — it reads as a sentence, not a name, so once files exist the file
|
|
149
|
+
* is the name. Two sibling outputs of one job are named by their folder.
|
|
150
|
+
*/
|
|
151
|
+
export function generationTitle(job: GenerationJob): string {
|
|
152
|
+
const named = recordedOutputName(job);
|
|
153
|
+
if (named) return named;
|
|
154
|
+
const outputs = generationOutputs(job);
|
|
155
|
+
if (outputs.length === 0) return shorten(job.label, 64);
|
|
156
|
+
const first = outputs[0] as GenerationOutput;
|
|
157
|
+
const folder = descriptiveFolder(first.path);
|
|
158
|
+
if (outputs.length > 1) {
|
|
159
|
+
return folder ? `${folder} · ${outputs.length} files` : `${outputs.length} files`;
|
|
160
|
+
}
|
|
161
|
+
// `image-1.png` names nothing on its own. A descriptive folder carries it
|
|
162
|
+
// (`motion/video.mp4`); a per-request hash folder carries nothing, so the
|
|
163
|
+
// prompt is a better name than an opaque id.
|
|
164
|
+
if (!GENERIC_OUTPUT_NAME.test(first.name)) return first.name;
|
|
165
|
+
return folder ? `${folder}/${first.name}` : shorten(job.label, 64);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const GENERIC_OUTPUT_NAME = /^(image|video|audio|model|panorama|output)(-\d+)?\./;
|
|
169
|
+
|
|
170
|
+
/** A hash/uuid segment names nothing to a human; a written word does. */
|
|
171
|
+
function descriptiveFolder(path: string): string | null {
|
|
172
|
+
const folder = path.split('/').slice(-2, -1)[0];
|
|
173
|
+
if (!folder) return null;
|
|
174
|
+
const opaque = /^[0-9a-f]{8,}$/i.test(folder) || /^[0-9a-f-]{16,}$/i.test(folder);
|
|
175
|
+
return opaque ? null : folder;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The name the submitting operation asked its output to be written as, when it
|
|
180
|
+
* recorded one. This is the naming the accept operation itself owns; the
|
|
181
|
+
* gallery only reads it.
|
|
182
|
+
*/
|
|
183
|
+
function recordedOutputName(job: GenerationJob): string | null {
|
|
184
|
+
const input = job.accept?.input;
|
|
185
|
+
if (!input || typeof input !== 'object') return null;
|
|
186
|
+
const value = (input as Record<string, unknown>)['outputName'];
|
|
187
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The submitted prompt, when the job's own recorded operation input carries
|
|
192
|
+
* one. Providers keep native request shapes, so this reads the recorded
|
|
193
|
+
* `accept`/`poll` input a level or two deep and gives up rather than guessing —
|
|
194
|
+
* a card never invents prompt text.
|
|
195
|
+
*/
|
|
196
|
+
export function generationPrompt(job: GenerationJob): string | null {
|
|
197
|
+
for (const root of [job.accept?.input, job.poll.input]) {
|
|
198
|
+
const found = findPrompt(root, 0);
|
|
199
|
+
if (found) return found;
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function findPrompt(value: unknown, depth: number): string | null {
|
|
205
|
+
if (depth > 3 || typeof value !== 'object' || value === null) return null;
|
|
206
|
+
const record = value as Record<string, unknown>;
|
|
207
|
+
const direct = record['prompt'];
|
|
208
|
+
if (typeof direct === 'string' && direct.trim().length > 0) return direct.trim();
|
|
209
|
+
for (const nested of Object.values(record)) {
|
|
210
|
+
const found = findPrompt(nested, depth + 1);
|
|
211
|
+
if (found) return found;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Provider-native model/endpoint identity, e.g. `fal-ai/nano-banana-2/edit`. */
|
|
217
|
+
export function generationModelLabel(job: GenerationJob): string {
|
|
218
|
+
return job.operation;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function generationRouteLabel(job: GenerationJob): string {
|
|
222
|
+
if (job.mode === 'managed') return 'VGAI subscription';
|
|
223
|
+
if (job.mode === 'direct') return 'Your provider key';
|
|
224
|
+
return 'Mock';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The charge, settled where the provider reported one, estimate otherwise. */
|
|
228
|
+
export function generationBillingLabel(billing: GenerationBilling): string {
|
|
229
|
+
if (billing.route === 'mock') return 'Free · mock';
|
|
230
|
+
if (billing.route === 'managed') {
|
|
231
|
+
if (billing.settledCredits !== undefined) return `${billing.settledCredits} credits`;
|
|
232
|
+
if (billing.estimatedCredits !== undefined) return `Up to ${billing.estimatedCredits} credits`;
|
|
233
|
+
return 'Credit estimate unavailable';
|
|
234
|
+
}
|
|
235
|
+
if (billing.settledAmount !== undefined) return `$${billing.settledAmount.toFixed(3)}`;
|
|
236
|
+
if (billing.estimatedAmount !== undefined) return `Up to $${billing.estimatedAmount.toFixed(3)}`;
|
|
237
|
+
return 'USD estimate unavailable';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** True once the provider has settled the charge, so the figure is final. */
|
|
241
|
+
export function generationBillingSettled(billing: GenerationBilling): boolean {
|
|
242
|
+
if (billing.route === 'mock') return true;
|
|
243
|
+
if (billing.route === 'managed') return billing.settledCredits !== undefined;
|
|
244
|
+
return billing.settledAmount !== undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Local to the gallery: coarse age, at the resolution a card can show. */
|
|
248
|
+
export function generationAge(iso: string): string {
|
|
249
|
+
const minutes = Math.floor((Date.now() - Date.parse(iso)) / 60_000);
|
|
250
|
+
if (!Number.isFinite(minutes) || minutes < 1) return 'just now';
|
|
251
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
252
|
+
const hours = Math.floor(minutes / 60);
|
|
253
|
+
if (hours < 24) return `${hours}h ago`;
|
|
254
|
+
const days = Math.floor(hours / 24);
|
|
255
|
+
if (days < 7) return `${days}d ago`;
|
|
256
|
+
return `${Math.floor(days / 7)}w ago`;
|
|
257
|
+
}
|