@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,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generations — the project's generated ASSETS, shown as the assets they are.
|
|
3
|
+
*
|
|
4
|
+
* Every card's subject is the file the job produced: the image, the video with
|
|
5
|
+
* its own controls, the model's rendered thumbnail. Job vocabulary (state,
|
|
6
|
+
* model, charge, prompt) frames that preview instead of replacing it, and the
|
|
7
|
+
* preview is never fabricated — a file the browser cannot show honestly gets a
|
|
8
|
+
* named file row, not a decorative stand-in.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { faFile, faFileLines, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
|
12
|
+
import type { GenerationJob } from '@volter/editor-sdk/generations';
|
|
13
|
+
import { useState, useSyncExternalStore } from 'react';
|
|
14
|
+
import './GenerationGallery.css';
|
|
15
|
+
import { assetCapabilities, assetDocumentKind } from '@volter/editor-core/asset-workflow/asset-capabilities';
|
|
16
|
+
import { openAssetDocument } from '@volter/editor-core/components/asset-documents';
|
|
17
|
+
import { AudioAssetThumb, ModelThumbnail } from '@volter/editor-core/components/asset-thumbnails';
|
|
18
|
+
import { editorConsole } from '@volter/editor-core/editor-console';
|
|
19
|
+
import { useEditorStore } from '@volter/editor-core/editor-runtime';
|
|
20
|
+
import { modelThumbnailFormat } from '@volter/editor-core/model-thumbnail';
|
|
21
|
+
import {
|
|
22
|
+
Button,
|
|
23
|
+
EditorBadge,
|
|
24
|
+
EditorBanner,
|
|
25
|
+
EditorIcon,
|
|
26
|
+
EditorToolbar,
|
|
27
|
+
Spacer,
|
|
28
|
+
StateSurface,
|
|
29
|
+
type StateSurfaceTone,
|
|
30
|
+
Text,
|
|
31
|
+
} from '@volter/editor-sdk/widgets';
|
|
32
|
+
import { openGenerationCreateDocument, openGenerationDocument } from './generation-documents';
|
|
33
|
+
import {
|
|
34
|
+
acceptGenerationJob,
|
|
35
|
+
forgetGenerationJob,
|
|
36
|
+
generationJobIsActive,
|
|
37
|
+
generationJobIsUnread,
|
|
38
|
+
generationJobsError,
|
|
39
|
+
generationJobsSnapshot,
|
|
40
|
+
markGenerationJobRead,
|
|
41
|
+
pollActiveGenerationJobs,
|
|
42
|
+
refreshGenerationJobs,
|
|
43
|
+
subscribeGenerationJobs,
|
|
44
|
+
} from './generation-jobs';
|
|
45
|
+
import {
|
|
46
|
+
GENERATION_STATE_LABEL,
|
|
47
|
+
type GenerationCardState,
|
|
48
|
+
type GenerationOutput,
|
|
49
|
+
generationAge,
|
|
50
|
+
generationBillingLabel,
|
|
51
|
+
generationBillingSettled,
|
|
52
|
+
generationCardState,
|
|
53
|
+
generationModelLabel,
|
|
54
|
+
generationOutputs,
|
|
55
|
+
generationPrompt,
|
|
56
|
+
generationRemovable,
|
|
57
|
+
generationRouteLabel,
|
|
58
|
+
generationSaveRetryable,
|
|
59
|
+
generationTitle,
|
|
60
|
+
} from './generation-presentation';
|
|
61
|
+
|
|
62
|
+
/** One produced file, painted the way that file can honestly be shown. */
|
|
63
|
+
function OutputPreview({
|
|
64
|
+
output,
|
|
65
|
+
onOpen,
|
|
66
|
+
}: {
|
|
67
|
+
output: GenerationOutput;
|
|
68
|
+
onOpen?: (() => void) | undefined;
|
|
69
|
+
}) {
|
|
70
|
+
const src = output.url;
|
|
71
|
+
|
|
72
|
+
if (src && output.kind === 'image') {
|
|
73
|
+
const preview = (
|
|
74
|
+
<img className="vgai-generation-output-media" src={src} alt={output.name} loading="lazy" />
|
|
75
|
+
);
|
|
76
|
+
return onOpen ? (
|
|
77
|
+
// The preview itself opens the asset — the gesture a gallery implies.
|
|
78
|
+
<Button
|
|
79
|
+
variant="ghost"
|
|
80
|
+
className="vgai-generation-output-open"
|
|
81
|
+
onClick={onOpen}
|
|
82
|
+
aria-label={`Open ${output.name}`}
|
|
83
|
+
>
|
|
84
|
+
{preview}
|
|
85
|
+
</Button>
|
|
86
|
+
) : (
|
|
87
|
+
preview
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (src && output.kind === 'video') {
|
|
91
|
+
return (
|
|
92
|
+
// Deliberately no autoplay and no `muted` autoplay trick: a gallery that
|
|
93
|
+
// starts sound is the failure this panel is replacing.
|
|
94
|
+
<video
|
|
95
|
+
className="vgai-generation-output-media"
|
|
96
|
+
src={src}
|
|
97
|
+
controls
|
|
98
|
+
preload="metadata"
|
|
99
|
+
playsInline
|
|
100
|
+
title={output.name}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (src && output.kind === 'audio') {
|
|
105
|
+
return (
|
|
106
|
+
<div className="vgai-generation-output-audio">
|
|
107
|
+
<AudioAssetThumb url={src} name={output.name} />
|
|
108
|
+
<audio src={src} controls preload="metadata" title={output.name} />
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (output.url && output.kind === 'model' && modelThumbnailFormat(output.url)) {
|
|
113
|
+
return <ModelThumbnail url={output.url} />;
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<div className="vgai-generation-output-file">
|
|
117
|
+
<EditorIcon icon={output.kind === 'model' ? faFile : faFileLines} />
|
|
118
|
+
<Text variant="caption" tone="muted">
|
|
119
|
+
{output.name}
|
|
120
|
+
</Text>
|
|
121
|
+
{onOpen && (
|
|
122
|
+
<Button size="compact" variant="ghost" onClick={onOpen}>
|
|
123
|
+
Open file
|
|
124
|
+
</Button>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const STATE_TONE: Record<GenerationCardState, StateSurfaceTone> = {
|
|
131
|
+
queued: 'loading',
|
|
132
|
+
running: 'loading',
|
|
133
|
+
saving: 'loading',
|
|
134
|
+
'save-failed': 'error',
|
|
135
|
+
ready: 'success',
|
|
136
|
+
'no-asset': 'neutral',
|
|
137
|
+
failed: 'error',
|
|
138
|
+
cancelled: 'neutral',
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** What each state means, in a game developer's terms. */
|
|
142
|
+
const STATE_DESCRIPTION: Record<GenerationCardState, (job: GenerationJob) => string> = {
|
|
143
|
+
queued: (job) =>
|
|
144
|
+
job.queuePosition === undefined
|
|
145
|
+
? 'Waiting for the provider to start.'
|
|
146
|
+
: `Position ${job.queuePosition} in the provider queue.`,
|
|
147
|
+
running: (job) => job.message ?? 'The provider is working on it.',
|
|
148
|
+
saving: () => 'Downloading the finished output into this project.',
|
|
149
|
+
// The provider's own result is fine; the DOWNLOAD into the project is what
|
|
150
|
+
// failed, and the server has stopped trying. Nothing here promises another
|
|
151
|
+
// automatic attempt — the retry is the button.
|
|
152
|
+
'save-failed': (job) => job.pollError ?? 'The finished output could not be saved.',
|
|
153
|
+
ready: () => 'Saved into this project.',
|
|
154
|
+
'no-asset': () => 'The operation finished without a file this project can hold.',
|
|
155
|
+
failed: (job) => job.message ?? 'The provider reported a failure.',
|
|
156
|
+
cancelled: () => 'This generation was cancelled.',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** The media region while a job has produced nothing to show yet. */
|
|
160
|
+
function StateMedia({ job, state }: { job: GenerationJob; state: GenerationCardState }) {
|
|
161
|
+
const pending = state === 'queued' || state === 'running' || state === 'saving';
|
|
162
|
+
const failing = state === 'failed' || state === 'save-failed';
|
|
163
|
+
return (
|
|
164
|
+
<div className="vgai-generation-state-media" data-state={state}>
|
|
165
|
+
<StateSurface
|
|
166
|
+
compact
|
|
167
|
+
tone={STATE_TONE[state]}
|
|
168
|
+
{...(failing ? { icon: <EditorIcon icon={faTriangleExclamation} /> } : {})}
|
|
169
|
+
title={GENERATION_STATE_LABEL[state]}
|
|
170
|
+
description={STATE_DESCRIPTION[state](job)}
|
|
171
|
+
/>
|
|
172
|
+
{pending && (
|
|
173
|
+
// Determinate ONLY when the provider reported progress; otherwise the
|
|
174
|
+
// native indeterminate bar, which claims nothing.
|
|
175
|
+
<progress
|
|
176
|
+
className="vgai-generation-progress"
|
|
177
|
+
aria-label={`${job.label} progress`}
|
|
178
|
+
{...(state === 'running' && job.progress !== undefined
|
|
179
|
+
? { value: job.progress, max: 1 }
|
|
180
|
+
: {})}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function CardActions({
|
|
188
|
+
job,
|
|
189
|
+
busy,
|
|
190
|
+
error,
|
|
191
|
+
run,
|
|
192
|
+
}: {
|
|
193
|
+
job: GenerationJob;
|
|
194
|
+
busy: boolean;
|
|
195
|
+
error: string | null;
|
|
196
|
+
run: (action: () => Promise<void>) => void;
|
|
197
|
+
}) {
|
|
198
|
+
return (
|
|
199
|
+
<div className="vgai-generation-card-actions">
|
|
200
|
+
{generationSaveRetryable(job, error) && (
|
|
201
|
+
// There is NO add-to-project gate: the server accepts a finished
|
|
202
|
+
// output on its own. This appears only once a save has actually
|
|
203
|
+
// failed, and runs that same accept operation again by hand.
|
|
204
|
+
<Button
|
|
205
|
+
size="compact"
|
|
206
|
+
variant="primary"
|
|
207
|
+
disabled={busy}
|
|
208
|
+
onClick={() => run(() => acceptGenerationJob(job))}
|
|
209
|
+
>
|
|
210
|
+
{busy ? 'Saving…' : 'Retry save'}
|
|
211
|
+
</Button>
|
|
212
|
+
)}
|
|
213
|
+
<Button size="compact" variant="ghost" onClick={() => openGenerationDocument(job)}>
|
|
214
|
+
Details
|
|
215
|
+
</Button>
|
|
216
|
+
{generationRemovable(job) && (
|
|
217
|
+
<Button
|
|
218
|
+
size="compact"
|
|
219
|
+
variant="ghost"
|
|
220
|
+
disabled={busy}
|
|
221
|
+
onClick={() => run(() => forgetGenerationJob(job.id))}
|
|
222
|
+
>
|
|
223
|
+
Remove
|
|
224
|
+
</Button>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function GenerationCard({
|
|
231
|
+
job,
|
|
232
|
+
busy,
|
|
233
|
+
error,
|
|
234
|
+
run,
|
|
235
|
+
}: {
|
|
236
|
+
job: GenerationJob;
|
|
237
|
+
busy: boolean;
|
|
238
|
+
error: string | null;
|
|
239
|
+
run: (action: () => Promise<void>) => void;
|
|
240
|
+
}) {
|
|
241
|
+
const store = useEditorStore();
|
|
242
|
+
const state = generationCardState(job);
|
|
243
|
+
const outputs = generationOutputs(job);
|
|
244
|
+
const prompt = generationPrompt(job);
|
|
245
|
+
const unread = generationJobIsUnread(job);
|
|
246
|
+
const openOutput = (output: GenerationOutput): (() => void) | undefined => {
|
|
247
|
+
if (!output.url) return undefined;
|
|
248
|
+
const kind = assetDocumentKind(assetCapabilities(output.name));
|
|
249
|
+
if (!kind) return undefined;
|
|
250
|
+
return () => openAssetDocument(store, output.url as string, kind);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<article
|
|
255
|
+
className="vgai-generation-card"
|
|
256
|
+
data-state={state}
|
|
257
|
+
aria-busy={generationJobIsActive(job)}
|
|
258
|
+
aria-label={generationTitle(job)}
|
|
259
|
+
onClickCapture={() => {
|
|
260
|
+
if (unread) {
|
|
261
|
+
void markGenerationJobRead(job.id).catch((cause) => {
|
|
262
|
+
editorConsole.warn(
|
|
263
|
+
`Could not mark generation as seen: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
264
|
+
'generations',
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
<div className="vgai-generation-card-media" data-outputs={Math.min(outputs.length, 4)}>
|
|
271
|
+
{outputs.length === 0 ? (
|
|
272
|
+
<StateMedia job={job} state={state} />
|
|
273
|
+
) : (
|
|
274
|
+
outputs.map((output) => (
|
|
275
|
+
<div className="vgai-generation-output" key={output.path} title={output.path}>
|
|
276
|
+
<OutputPreview output={output} onOpen={openOutput(output)} />
|
|
277
|
+
</div>
|
|
278
|
+
))
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<div className="vgai-generation-card-body">
|
|
283
|
+
<div className="vgai-generation-card-heading">
|
|
284
|
+
<Text variant="label" truncate title={generationTitle(job)}>
|
|
285
|
+
{generationTitle(job)}
|
|
286
|
+
</Text>
|
|
287
|
+
<EditorBadge className="vgai-generation-chip" data-state={state}>
|
|
288
|
+
{GENERATION_STATE_LABEL[state]}
|
|
289
|
+
{state === 'running' && job.progress !== undefined
|
|
290
|
+
? ` · ${Math.round(job.progress * 100)}%`
|
|
291
|
+
: ''}
|
|
292
|
+
</EditorBadge>
|
|
293
|
+
{unread && (
|
|
294
|
+
<EditorBadge className="vgai-generation-chip" data-state="new">
|
|
295
|
+
New
|
|
296
|
+
</EditorBadge>
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<Text variant="caption" tone="muted" truncate title={generationModelLabel(job)}>
|
|
301
|
+
{job.provider} · {generationModelLabel(job)}
|
|
302
|
+
</Text>
|
|
303
|
+
<Text variant="caption" tone="dim">
|
|
304
|
+
{generationBillingLabel(job.billing)}
|
|
305
|
+
{generationBillingSettled(job.billing) ? '' : ' (est.)'} · {generationRouteLabel(job)} ·{' '}
|
|
306
|
+
{generationAge(job.updatedAt)}
|
|
307
|
+
</Text>
|
|
308
|
+
|
|
309
|
+
{prompt && (
|
|
310
|
+
<p className="vgai-generation-card-prompt" title={prompt}>
|
|
311
|
+
{prompt}
|
|
312
|
+
</p>
|
|
313
|
+
)}
|
|
314
|
+
|
|
315
|
+
{(error ?? job.pollError) && (
|
|
316
|
+
// An ACTIVE job's poll error is transient — the server is still
|
|
317
|
+
// polling, and saying so is true. A finished job's error is a failed
|
|
318
|
+
// SAVE, whose automatic attempts the server has already spent, so the
|
|
319
|
+
// text promises nothing and the retry button carries it instead.
|
|
320
|
+
<EditorBanner
|
|
321
|
+
tone={error || state === 'save-failed' ? 'error' : 'warning'}
|
|
322
|
+
className="vgai-generation-card-alert"
|
|
323
|
+
>
|
|
324
|
+
{error ??
|
|
325
|
+
(state === 'save-failed'
|
|
326
|
+
? `Could not save into the project: ${job.pollError}`
|
|
327
|
+
: `Polling will retry: ${job.pollError}`)}
|
|
328
|
+
</EditorBanner>
|
|
329
|
+
)}
|
|
330
|
+
|
|
331
|
+
<CardActions job={job} busy={busy} error={error} run={run} />
|
|
332
|
+
</div>
|
|
333
|
+
</article>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function GenerationActivity() {
|
|
338
|
+
const { jobs } = useSyncExternalStore(
|
|
339
|
+
subscribeGenerationJobs,
|
|
340
|
+
generationJobsSnapshot,
|
|
341
|
+
generationJobsSnapshot,
|
|
342
|
+
);
|
|
343
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
344
|
+
const [actionErrors, setActionErrors] = useState<Record<string, string>>({});
|
|
345
|
+
const activeCount = jobs.filter(generationJobIsActive).length;
|
|
346
|
+
const savingCount = jobs.filter((job) => generationCardState(job) === 'saving').length;
|
|
347
|
+
|
|
348
|
+
const run = async (id: string, action: () => Promise<void>) => {
|
|
349
|
+
setBusy(id);
|
|
350
|
+
setActionErrors((current) => {
|
|
351
|
+
const next = { ...current };
|
|
352
|
+
delete next[id];
|
|
353
|
+
return next;
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
await action();
|
|
357
|
+
} catch (cause) {
|
|
358
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
359
|
+
setActionErrors((current) => ({ ...current, [id]: message }));
|
|
360
|
+
} finally {
|
|
361
|
+
setBusy(null);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const storeError = generationJobsError();
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<div className="vgai-generation-gallery">
|
|
369
|
+
<EditorToolbar label="Generations" compact className="vgai-generation-gallery-bar">
|
|
370
|
+
<Text variant="label">Generations</Text>
|
|
371
|
+
<Text variant="caption" tone="muted">
|
|
372
|
+
{jobs.length} {jobs.length === 1 ? 'generation' : 'generations'}
|
|
373
|
+
{activeCount > 0 ? ` · ${activeCount} running` : ''}
|
|
374
|
+
{savingCount > 0 ? ` · ${savingCount} saving` : ''}
|
|
375
|
+
</Text>
|
|
376
|
+
<Spacer />
|
|
377
|
+
<Button
|
|
378
|
+
size="compact"
|
|
379
|
+
variant="ghost"
|
|
380
|
+
onClick={() => void pollActiveGenerationJobs().then(refreshGenerationJobs)}
|
|
381
|
+
>
|
|
382
|
+
Refresh
|
|
383
|
+
</Button>
|
|
384
|
+
<Button size="compact" variant="primary" onClick={() => openGenerationCreateDocument()}>
|
|
385
|
+
New asset
|
|
386
|
+
</Button>
|
|
387
|
+
</EditorToolbar>
|
|
388
|
+
|
|
389
|
+
{storeError && (
|
|
390
|
+
<EditorBanner tone="error" className="vgai-generation-gallery-alert">
|
|
391
|
+
{storeError}
|
|
392
|
+
</EditorBanner>
|
|
393
|
+
)}
|
|
394
|
+
|
|
395
|
+
{jobs.length === 0 ? (
|
|
396
|
+
<StateSurface
|
|
397
|
+
className="vgai-generation-gallery-empty"
|
|
398
|
+
title="No generated assets yet"
|
|
399
|
+
description="Preview generated images, video, audio and models here. Create an asset to get started."
|
|
400
|
+
action={
|
|
401
|
+
<Button size="compact" variant="primary" onClick={() => openGenerationCreateDocument()}>
|
|
402
|
+
New asset
|
|
403
|
+
</Button>
|
|
404
|
+
}
|
|
405
|
+
/>
|
|
406
|
+
) : (
|
|
407
|
+
<section className="vgai-generation-grid" aria-label="Generated assets">
|
|
408
|
+
{jobs.map((job) => (
|
|
409
|
+
<GenerationCard
|
|
410
|
+
key={job.id}
|
|
411
|
+
job={job}
|
|
412
|
+
busy={busy === job.id}
|
|
413
|
+
error={actionErrors[job.id] ?? null}
|
|
414
|
+
run={(action) => void run(job.id, action)}
|
|
415
|
+
/>
|
|
416
|
+
))}
|
|
417
|
+
</section>
|
|
418
|
+
)}
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/* Generations utility — a responsive asset gallery (GenerationActivity.tsx).
|
|
2
|
+
*
|
|
3
|
+
* The card is preview-first: a fixed-aspect media box holds the generated file
|
|
4
|
+
* at `contain` (portrait and landscape both survive intact), and the job's text
|
|
5
|
+
* sits under it in a fixed reading order. The grid is `auto-fill` so the same
|
|
6
|
+
* panel works docked narrow at the side and wide across the bottom; a container
|
|
7
|
+
* query drops the card to a single readable column in a genuinely narrow dock.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.vgai-generation-gallery {
|
|
11
|
+
display: flex;
|
|
12
|
+
height: 100%;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
color: var(--vgai-content-primary);
|
|
15
|
+
/* `size`, not `inline-size`: the cards answer to the drawer's HEIGHT as well
|
|
16
|
+
* as its width — a short bottom drawer gets a shorter preview so the name,
|
|
17
|
+
* cost and actions still land inside it. */
|
|
18
|
+
container-type: size;
|
|
19
|
+
}
|
|
20
|
+
.vgai-generation-gallery .vgai-toolbar {
|
|
21
|
+
flex: 0 0 auto;
|
|
22
|
+
}
|
|
23
|
+
.vgai-generation-gallery-bar {
|
|
24
|
+
gap: var(--vgai-space-2);
|
|
25
|
+
}
|
|
26
|
+
.vgai-generation-gallery-alert {
|
|
27
|
+
margin: var(--vgai-space-3);
|
|
28
|
+
}
|
|
29
|
+
.vgai-generation-gallery-empty {
|
|
30
|
+
margin: auto;
|
|
31
|
+
max-width: 420px;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.vgai-generation-grid {
|
|
36
|
+
display: grid;
|
|
37
|
+
min-height: 0;
|
|
38
|
+
flex: 1;
|
|
39
|
+
align-content: start;
|
|
40
|
+
gap: var(--vgai-space-3);
|
|
41
|
+
padding: var(--vgai-space-3);
|
|
42
|
+
overflow-y: auto;
|
|
43
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
44
|
+
grid-auto-rows: max-content;
|
|
45
|
+
}
|
|
46
|
+
@container (max-width: 380px) {
|
|
47
|
+
.vgai-generation-grid {
|
|
48
|
+
grid-template-columns: 1fr;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
.vgai-generation-card {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
border: var(--vgai-stroke-resting) solid var(--vgai-boundary-default);
|
|
56
|
+
border-radius: var(--vgai-radius-md);
|
|
57
|
+
background: var(--vgai-surface-panel);
|
|
58
|
+
}
|
|
59
|
+
.vgai-generation-card[data-state="failed"],
|
|
60
|
+
.vgai-generation-card[data-state="save-failed"] {
|
|
61
|
+
border-color: var(--vgai-danger-muted);
|
|
62
|
+
}
|
|
63
|
+
.vgai-generation-card[data-state="ready"] {
|
|
64
|
+
border-color: var(--vgai-boundary-strong);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* The preview. One output fills the box; several share it as a tile grid, so a
|
|
68
|
+
* multi-output job shows every file it produced without a second surface. */
|
|
69
|
+
.vgai-generation-card-media {
|
|
70
|
+
display: grid;
|
|
71
|
+
/* `flex-shrink: 0` and the height cap are load-bearing TOGETHER: in the
|
|
72
|
+
* ordinary ~260px bottom drawer the card was squeezed to a ~50px strip with
|
|
73
|
+
* every piece of metadata hidden, and an uncapped 4:3 box pushed the text
|
|
74
|
+
* out of the drawer instead. */
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
height: 160px;
|
|
77
|
+
max-height: 160px;
|
|
78
|
+
gap: var(--vgai-stroke-active);
|
|
79
|
+
background: var(--vgai-bg-inset);
|
|
80
|
+
grid-template-columns: 1fr;
|
|
81
|
+
}
|
|
82
|
+
.vgai-generation-card-media[data-outputs="2"],
|
|
83
|
+
.vgai-generation-card-media[data-outputs="3"],
|
|
84
|
+
.vgai-generation-card-media[data-outputs="4"] {
|
|
85
|
+
grid-template-columns: 1fr 1fr;
|
|
86
|
+
}
|
|
87
|
+
.vgai-generation-output {
|
|
88
|
+
display: flex;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
min-height: 0;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
padding: var(--vgai-space-1);
|
|
95
|
+
}
|
|
96
|
+
/* `contain`, never `cover`: cropping a generated frame hides exactly the part
|
|
97
|
+
* a game developer is judging. */
|
|
98
|
+
.vgai-generation-output-media {
|
|
99
|
+
display: block;
|
|
100
|
+
width: 100%;
|
|
101
|
+
height: 100%;
|
|
102
|
+
max-width: 100%;
|
|
103
|
+
max-height: 100%;
|
|
104
|
+
border-radius: var(--vgai-radius-sm);
|
|
105
|
+
object-fit: contain;
|
|
106
|
+
}
|
|
107
|
+
/* The preview's own click target: a real Button stripped back to the image it
|
|
108
|
+
* contains, so the gallery's most obvious gesture is still a focusable control. */
|
|
109
|
+
.vgai-generation-output .vgai-generation-output-open.vgai-btn {
|
|
110
|
+
display: flex;
|
|
111
|
+
width: 100%;
|
|
112
|
+
height: 100%;
|
|
113
|
+
max-height: 100%;
|
|
114
|
+
min-height: 0;
|
|
115
|
+
align-items: center;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
padding: 0;
|
|
118
|
+
border: 0;
|
|
119
|
+
background: transparent;
|
|
120
|
+
cursor: zoom-in;
|
|
121
|
+
}
|
|
122
|
+
video.vgai-generation-output-media {
|
|
123
|
+
width: 100%;
|
|
124
|
+
}
|
|
125
|
+
.vgai-generation-output-audio {
|
|
126
|
+
display: flex;
|
|
127
|
+
width: 100%;
|
|
128
|
+
height: 100%;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
gap: var(--vgai-space-2);
|
|
133
|
+
padding: var(--vgai-space-2);
|
|
134
|
+
}
|
|
135
|
+
.vgai-generation-output-audio audio {
|
|
136
|
+
width: 100%;
|
|
137
|
+
}
|
|
138
|
+
.vgai-generation-output-file {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: var(--vgai-space-2);
|
|
143
|
+
padding: var(--vgai-space-3);
|
|
144
|
+
color: var(--vgai-content-muted);
|
|
145
|
+
text-align: center;
|
|
146
|
+
overflow-wrap: anywhere;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.vgai-generation-state-media {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
align-items: center;
|
|
153
|
+
justify-content: center;
|
|
154
|
+
gap: var(--vgai-space-2);
|
|
155
|
+
padding: var(--vgai-space-3);
|
|
156
|
+
text-align: center;
|
|
157
|
+
}
|
|
158
|
+
.vgai-generation-progress {
|
|
159
|
+
width: 70%;
|
|
160
|
+
height: 4px;
|
|
161
|
+
accent-color: var(--vgai-accent);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.vgai-generation-card-body {
|
|
165
|
+
display: flex;
|
|
166
|
+
min-width: 0;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
gap: var(--vgai-space-1);
|
|
169
|
+
padding: var(--vgai-space-3);
|
|
170
|
+
}
|
|
171
|
+
.vgai-generation-card-heading {
|
|
172
|
+
display: flex;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: var(--vgai-space-2);
|
|
176
|
+
}
|
|
177
|
+
.vgai-generation-card-heading > .vgai-text {
|
|
178
|
+
min-width: 0;
|
|
179
|
+
flex: 1;
|
|
180
|
+
}
|
|
181
|
+
.vgai-generation-chip {
|
|
182
|
+
flex: 0 0 auto;
|
|
183
|
+
}
|
|
184
|
+
.vgai-generation-chip[data-state="ready"] {
|
|
185
|
+
color: var(--vgai-success);
|
|
186
|
+
border-color: var(--vgai-success-muted);
|
|
187
|
+
}
|
|
188
|
+
.vgai-generation-chip[data-state="failed"],
|
|
189
|
+
.vgai-generation-chip[data-state="save-failed"] {
|
|
190
|
+
color: var(--vgai-danger);
|
|
191
|
+
border-color: var(--vgai-danger-muted);
|
|
192
|
+
}
|
|
193
|
+
.vgai-generation-chip[data-state="queued"],
|
|
194
|
+
.vgai-generation-chip[data-state="running"],
|
|
195
|
+
.vgai-generation-chip[data-state="saving"],
|
|
196
|
+
.vgai-generation-chip[data-state="new"] {
|
|
197
|
+
color: var(--vgai-accent);
|
|
198
|
+
border-color: var(--vgai-accent-muted);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* The prompt is available, never dominant: two lines, full text on hover. */
|
|
202
|
+
.vgai-generation-card-prompt {
|
|
203
|
+
display: -webkit-box;
|
|
204
|
+
margin: var(--vgai-space-1) 0 0;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
color: var(--vgai-content-dim);
|
|
207
|
+
font-size: var(--vgai-font-sm);
|
|
208
|
+
line-height: var(--vgai-leading-normal);
|
|
209
|
+
-webkit-box-orient: vertical;
|
|
210
|
+
-webkit-line-clamp: 2;
|
|
211
|
+
line-clamp: 2;
|
|
212
|
+
}
|
|
213
|
+
.vgai-generation-card-alert {
|
|
214
|
+
margin-top: var(--vgai-space-2);
|
|
215
|
+
font-size: var(--vgai-font-sm);
|
|
216
|
+
}
|
|
217
|
+
.vgai-generation-card-actions {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-wrap: wrap;
|
|
220
|
+
gap: var(--vgai-space-2);
|
|
221
|
+
margin-top: var(--vgai-space-2);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@container (max-height: 300px) {
|
|
225
|
+
.vgai-generation-card-media {
|
|
226
|
+
height: 96px;
|
|
227
|
+
}
|
|
228
|
+
.vgai-generation-card-prompt {
|
|
229
|
+
display: none;
|
|
230
|
+
}
|
|
231
|
+
}
|