@treasuryspatial/prompt-ui-kit 0.1.14 → 0.1.21

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.
Files changed (44) hide show
  1. package/dist/FeedbackPanel.d.ts +20 -1
  2. package/dist/FeedbackPanel.d.ts.map +1 -1
  3. package/dist/FeedbackPanel.js +251 -25
  4. package/dist/FullscreenImage.d.ts +12 -1
  5. package/dist/FullscreenImage.d.ts.map +1 -1
  6. package/dist/FullscreenImage.js +319 -41
  7. package/dist/GalleryThumbCard.d.ts +2 -1
  8. package/dist/GalleryThumbCard.d.ts.map +1 -1
  9. package/dist/GalleryThumbCard.js +121 -3
  10. package/dist/ImageReferenceStrip.d.ts +9 -0
  11. package/dist/ImageReferenceStrip.d.ts.map +1 -0
  12. package/dist/ImageReferenceStrip.js +47 -0
  13. package/dist/PromptDebugPanel.d.ts +1 -2
  14. package/dist/PromptDebugPanel.d.ts.map +1 -1
  15. package/dist/PromptDebugPanel.js +74 -2
  16. package/dist/RenderHistoryPanel.d.ts +6 -1
  17. package/dist/RenderHistoryPanel.d.ts.map +1 -1
  18. package/dist/RenderHistoryPanel.js +108 -17
  19. package/dist/RenderImageStage.d.ts +10 -0
  20. package/dist/RenderImageStage.d.ts.map +1 -0
  21. package/dist/RenderImageStage.js +90 -0
  22. package/dist/RenderLightboxModal.d.ts +4 -2
  23. package/dist/RenderLightboxModal.d.ts.map +1 -1
  24. package/dist/RenderLightboxModal.js +54 -18
  25. package/dist/RenderPanel.d.ts +17 -16
  26. package/dist/RenderPanel.d.ts.map +1 -1
  27. package/dist/RenderPanel.js +178 -204
  28. package/dist/createRenderHistoryActions.d.ts +11 -0
  29. package/dist/createRenderHistoryActions.d.ts.map +1 -0
  30. package/dist/createRenderHistoryActions.js +63 -0
  31. package/dist/imageDetailPanels.d.ts +14 -0
  32. package/dist/imageDetailPanels.d.ts.map +1 -0
  33. package/dist/imageDetailPanels.js +128 -0
  34. package/dist/index.d.ts +7 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/useRenderImageFlow.d.ts +2 -2
  39. package/dist/useRenderImageFlow.d.ts.map +1 -1
  40. package/dist/useRenderImageFlow.js +1 -1
  41. package/dist/useRenderLightbox.d.ts +14 -2
  42. package/dist/useRenderLightbox.d.ts.map +1 -1
  43. package/dist/useRenderLightbox.js +333 -34
  44. package/package.json +7 -5
@@ -1,6 +1,6 @@
1
1
  import { type Dispatch, type SetStateAction } from 'react';
2
- import type { RenderHistoryEntry } from './RenderHistoryPanel';
3
- import { type RenderLightboxState, type UseRenderLightboxOptions } from './useRenderLightbox';
2
+ import type { RenderHistoryEntry } from './RenderHistoryPanel.js';
3
+ import { type RenderLightboxState, type UseRenderLightboxOptions } from './useRenderLightbox.js';
4
4
  export type RenderImageFlowEntry = Omit<RenderHistoryEntry, 'id'> & {
5
5
  id?: string;
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useRenderImageFlow.d.ts","sourceRoot":"","sources":["../src/useRenderImageFlow.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAqB,KAAK,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAIjH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAAG;IAClE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,wBAAwB,GAAG;IACjE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,gBAAgB,EAAE,QAAQ,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IACjE,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,mBAAmB,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,kBAAkB,CAAC;IACzE,QAAQ,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,UAAS,yBAA8B,KACtC,oBAyCF,CAAC"}
1
+ {"version":3,"file":"useRenderImageFlow.d.ts","sourceRoot":"","sources":["../src/useRenderImageFlow.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAqB,KAAK,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAIpH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAAG;IAClE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,wBAAwB,GAAG;IACjE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,gBAAgB,EAAE,QAAQ,CAAC,cAAc,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IACjE,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,mBAAmB,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,kBAAkB,CAAC;IACzE,QAAQ,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,UAAS,yBAA8B,KACtC,oBAyCF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useCallback, useState } from 'react';
3
- import { useRenderLightbox } from './useRenderLightbox';
3
+ import { useRenderLightbox } from './useRenderLightbox.js';
4
4
  const createHistoryId = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
5
5
  export const useRenderImageFlow = (options = {}) => {
6
6
  const { onImageRegistered, ...lightboxOptions } = options;
@@ -1,4 +1,5 @@
1
- import type { RenderHistoryEntry } from './RenderHistoryPanel';
1
+ import { type ComposerGallerySnapshot } from '@treasuryspatial/render-kit';
2
+ import type { RenderHistoryEntry } from './RenderHistoryPanel.js';
2
3
  export type RenderLightboxStatus = 'idle' | 'sending' | 'sent' | 'error';
3
4
  export type UseRenderLightboxOptions = {
4
5
  shareEndpoint?: string;
@@ -10,17 +11,28 @@ export type UseRenderLightboxOptions = {
10
11
  isAdminUser?: boolean;
11
12
  user?: string | null;
12
13
  name?: string | null;
14
+ surfaceId?: string | null;
15
+ mode?: string | null;
16
+ promptPackId?: string | null;
17
+ onRestartFromSnapshot?: (entry: RenderHistoryEntry, snapshot: ComposerGallerySnapshot) => void;
13
18
  };
14
19
  export type RenderLightboxState = {
15
20
  entry: RenderHistoryEntry | null;
16
21
  src: string;
17
22
  alt: string;
18
23
  status: RenderLightboxStatus;
24
+ statusText: string;
19
25
  shortId: string | null;
20
26
  publicUrl: string;
21
27
  open: (entry: RenderHistoryEntry | null, src: string, alt: string) => void;
22
28
  close: () => void;
23
- submitToGallery: () => Promise<void>;
29
+ shareImage: (entry?: RenderHistoryEntry | null, src?: string) => Promise<void>;
30
+ copyPrompt: (entry?: RenderHistoryEntry | null) => Promise<void>;
31
+ copyPublicLink: (entry?: RenderHistoryEntry | null, src?: string) => Promise<void>;
32
+ copyImageLink: (entry?: RenderHistoryEntry | null, src?: string) => Promise<void>;
33
+ downloadImage: (entry?: RenderHistoryEntry | null, src?: string) => void;
34
+ restartFromSnapshot: (entry?: RenderHistoryEntry | null) => Promise<void>;
35
+ submitToGallery: (entry?: RenderHistoryEntry | null, src?: string) => Promise<void>;
24
36
  };
25
37
  export declare const useRenderLightbox: (options?: UseRenderLightboxOptions) => RenderLightboxState;
26
38
  //# sourceMappingURL=useRenderLightbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useRenderLightbox.d.ts","sourceRoot":"","sources":["../src/useRenderLightbox.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzE,MAAM,MAAM,wBAAwB,GAAG;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,MAAM,CAAC;IACrD,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC;IACxE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,oBAAoB,CAAC;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3E,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,UAAS,wBAA6B,KAAG,mBAwI1E,CAAC"}
1
+ {"version":3,"file":"useRenderLightbox.d.ts","sourceRoot":"","sources":["../src/useRenderLightbox.ts"],"names":[],"mappings":"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"}
@@ -1,40 +1,196 @@
1
1
  'use client';
2
2
  import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { buildImageActionRecord, buildComposerImageApiUrl, isRestartableGallerySnapshot, resolveComposerEffectivePrompt, resolveComposerImageLinks, } from '@treasuryspatial/render-kit';
3
4
  export const useRenderLightbox = (options = {}) => {
4
- const { shareEndpoint = '/api/share/shortlink', galleryEndpoint = '/api/gallery/submit', encodeShareToken, resolveTitle, buildShareUrl, trackTelemetry, isAdminUser, user, name, } = options;
5
+ const { shareEndpoint = '/api/share/shortlink', galleryEndpoint = '/api/gallery/submit', encodeShareToken, resolveTitle, buildShareUrl, trackTelemetry, isAdminUser, user, name, surfaceId, mode, promptPackId, onRestartFromSnapshot, } = options;
5
6
  const [entry, setEntry] = useState(null);
6
7
  const [src, setSrc] = useState('');
7
8
  const [alt, setAlt] = useState('');
8
9
  const [shortId, setShortId] = useState(null);
9
10
  const [status, setStatus] = useState('idle');
11
+ const [statusText, setStatusText] = useState('');
12
+ const toAbsoluteUrl = useCallback((value) => {
13
+ if (!value)
14
+ return '';
15
+ if (/^(https?:|data:|blob:)/i.test(value))
16
+ return value;
17
+ if (value.startsWith('/') && typeof window !== 'undefined') {
18
+ return `${window.location.origin}${value}`;
19
+ }
20
+ return value;
21
+ }, []);
22
+ const getOrigin = useCallback(() => (typeof window !== 'undefined' ? window.location.origin : ''), []);
23
+ const isShareLikeUrl = useCallback((value) => {
24
+ if (!value)
25
+ return false;
26
+ if (/^(data:|blob:)/i.test(value))
27
+ return false;
28
+ try {
29
+ const parsed = new URL(value, typeof window !== 'undefined' ? window.location.origin : 'https://treasury.space');
30
+ return (parsed.pathname.startsWith('/i/') ||
31
+ parsed.pathname.startsWith('/g/') ||
32
+ parsed.pathname.startsWith('/gallery/') ||
33
+ parsed.pathname.startsWith('/composer/gallery/'));
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }, []);
39
+ const resolvePublicUrl = useCallback((targetEntry, targetSrc = src) => {
40
+ const targetShortId = (targetEntry?.id === entry?.id ? shortId : null) ||
41
+ targetEntry?.shortId ||
42
+ targetEntry?.snapshot?.shortId ||
43
+ null;
44
+ const links = resolveComposerImageLinks({
45
+ storage: targetEntry?.storage ?? targetEntry?.snapshot?.storage ?? null,
46
+ shortId: targetShortId,
47
+ origin: getOrigin(),
48
+ fallbackImageUrl: targetSrc,
49
+ });
50
+ if (links.pageUrl)
51
+ return links.pageUrl;
52
+ if (!targetEntry?.storage?.key) {
53
+ return targetSrc && /^https?:/i.test(targetSrc) ? targetSrc : '';
54
+ }
55
+ if (buildShareUrl)
56
+ return buildShareUrl(targetEntry.storage.key, targetShortId);
57
+ if (encodeShareToken) {
58
+ return `${getOrigin()}/i/${encodeShareToken(targetEntry.storage.key)}`;
59
+ }
60
+ return targetSrc && /^https?:/i.test(targetSrc) ? targetSrc : '';
61
+ }, [buildShareUrl, encodeShareToken, entry?.id, getOrigin, shortId, src]);
62
+ const resolveAssetUrl = useCallback((targetEntry, targetSrc = src) => {
63
+ const links = resolveComposerImageLinks({
64
+ storage: targetEntry?.storage ?? targetEntry?.snapshot?.storage ?? null,
65
+ shortId: targetEntry?.shortId || targetEntry?.snapshot?.shortId || (targetEntry?.id === entry?.id ? shortId : null),
66
+ origin: getOrigin(),
67
+ fallbackImageUrl: targetSrc,
68
+ });
69
+ if (links.directImageUrl)
70
+ return links.directImageUrl;
71
+ const canonicalStorageUrl = buildComposerImageApiUrl(targetEntry?.storage);
72
+ if (canonicalStorageUrl) {
73
+ return toAbsoluteUrl(canonicalStorageUrl);
74
+ }
75
+ const storageUrl = String(targetEntry?.storage?.url || '').trim();
76
+ if (storageUrl && !isShareLikeUrl(storageUrl)) {
77
+ if (/^https?:/i.test(storageUrl) || /^data:/i.test(storageUrl) || /^blob:/i.test(storageUrl)) {
78
+ return storageUrl;
79
+ }
80
+ if (storageUrl.startsWith('/')) {
81
+ return toAbsoluteUrl(storageUrl);
82
+ }
83
+ }
84
+ if (targetEntry?.storage?.key && typeof window !== 'undefined') {
85
+ return `${window.location.origin}/api/images/image?key=${encodeURIComponent(targetEntry.storage.key)}`;
86
+ }
87
+ if (targetSrc && (/^https?:/i.test(targetSrc) || /^data:/i.test(targetSrc) || /^blob:/i.test(targetSrc))) {
88
+ return targetSrc;
89
+ }
90
+ return '';
91
+ }, [entry?.id, getOrigin, isShareLikeUrl, shortId, src, toAbsoluteUrl]);
92
+ const publicUrl = useMemo(() => resolvePublicUrl(entry, src), [entry, resolvePublicUrl, src]);
93
+ const buildEntrySnapshot = useCallback((targetEntry) => {
94
+ if (targetEntry.snapshot)
95
+ return targetEntry.snapshot;
96
+ return {
97
+ id: targetEntry.snapshotId || targetEntry.id,
98
+ operationId: targetEntry.operationId || null,
99
+ interactionId: targetEntry.interactionId || null,
100
+ requestKind: targetEntry.requestKind || (targetEntry.kind === 'capture' ? 'render' : 'adapt'),
101
+ modeId: targetEntry.modeId || mode || null,
102
+ promptPackId: targetEntry.promptPackId || promptPackId || null,
103
+ title: targetEntry.title || null,
104
+ prompt: targetEntry.prompt || null,
105
+ fullPrompt: targetEntry.fullPrompt || null,
106
+ manifest: targetEntry.manifest ?? null,
107
+ galleryStatus: targetEntry.galleryStatus || null,
108
+ referenceImages: targetEntry.referenceImages ?? null,
109
+ baseImage: targetEntry.baseImage ?? null,
110
+ storage: targetEntry.storage
111
+ ? {
112
+ bucket: targetEntry.storage.bucket,
113
+ key: targetEntry.storage.key,
114
+ metaKey: targetEntry.storage.metaKey ?? null,
115
+ url: targetEntry.storage.url ?? null,
116
+ }
117
+ : null,
118
+ };
119
+ }, [mode, promptPackId]);
120
+ const ensureShortlink = useCallback(async (targetEntry) => {
121
+ if (!targetEntry?.storage?.key || !shareEndpoint)
122
+ return targetEntry?.shortId || targetEntry?.snapshot?.shortId || '';
123
+ const existing = targetEntry.shortId || targetEntry.snapshot?.shortId || (targetEntry.id === entry?.id ? shortId : null);
124
+ if (existing)
125
+ return existing;
126
+ const response = await fetch(shareEndpoint, {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body: JSON.stringify({
130
+ key: targetEntry.storage.key,
131
+ metaKey: targetEntry.storage.metaKey ?? null,
132
+ title: resolveTitle?.(targetEntry) ?? targetEntry.title ?? '',
133
+ user: user ?? '',
134
+ name: name ?? '',
135
+ }),
136
+ });
137
+ const data = await response.json().catch(() => null);
138
+ const nextShortId = typeof data?.shortId === 'string' ? data.shortId : '';
139
+ if (nextShortId && targetEntry.id === entry?.id)
140
+ setShortId(nextShortId);
141
+ return nextShortId;
142
+ }, [entry?.id, name, resolveTitle, shareEndpoint, shortId, user]);
143
+ const buildTelemetryPayload = useCallback((payload = {}, targetEntry = entry, targetPublicUrl = publicUrl, imageActionKind, imageActionStatus) => ({
144
+ surfaceId: surfaceId ?? undefined,
145
+ mode: mode ?? undefined,
146
+ promptPackId: promptPackId ?? undefined,
147
+ entryId: targetEntry?.id,
148
+ snapshotId: targetEntry?.snapshotId || targetEntry?.snapshot?.id,
149
+ operationId: targetEntry?.operationId || targetEntry?.snapshot?.operationId,
150
+ requestKind: targetEntry?.requestKind || targetEntry?.snapshot?.requestKind,
151
+ galleryStatus: targetEntry?.galleryStatus || targetEntry?.snapshot?.galleryStatus,
152
+ title: targetEntry?.title,
153
+ storageKey: targetEntry?.storage?.key,
154
+ storageUrl: targetEntry?.storage?.url,
155
+ publicUrl: targetPublicUrl || undefined,
156
+ imageAction: targetEntry
157
+ ? buildImageActionRecord({
158
+ snapshot: targetEntry.snapshot ?? null,
159
+ imageRecord: targetEntry,
160
+ kind: imageActionKind,
161
+ status: imageActionStatus,
162
+ publicUrl: targetPublicUrl || undefined,
163
+ })
164
+ : undefined,
165
+ ...payload,
166
+ }), [entry, mode, promptPackId, publicUrl, surfaceId]);
167
+ const copyText = useCallback(async (value, eventName, targetEntry = entry, targetPublicUrl) => {
168
+ if (!value)
169
+ return;
170
+ try {
171
+ await navigator.clipboard.writeText(value);
172
+ trackTelemetry?.(eventName, buildTelemetryPayload(targetPublicUrl ? { publicUrl: targetPublicUrl } : {}, targetEntry, targetPublicUrl, eventName === 'snapshot_copy_prompt' ? 'prompt.copied' : 'image.shared', 'success'));
173
+ }
174
+ catch {
175
+ trackTelemetry?.(`${eventName}_error`, buildTelemetryPayload(targetPublicUrl ? { publicUrl: targetPublicUrl } : {}, targetEntry, targetPublicUrl, eventName === 'snapshot_copy_prompt' ? 'prompt.copied' : 'image.shared', 'error'));
176
+ }
177
+ }, [buildTelemetryPayload, entry, trackTelemetry]);
10
178
  const open = useCallback((next, nextSrc, nextAlt) => {
11
179
  setEntry(next);
12
180
  setSrc(nextSrc);
13
181
  setAlt(nextAlt);
14
182
  setStatus('idle');
15
- }, []);
183
+ setStatusText('');
184
+ trackTelemetry?.('gallery_open', buildTelemetryPayload({}, next, resolvePublicUrl(next, nextSrc), 'image.opened', 'success'));
185
+ }, [buildTelemetryPayload, resolvePublicUrl, trackTelemetry]);
16
186
  const close = useCallback(() => {
187
+ trackTelemetry?.('image_lightbox_close', buildTelemetryPayload({}, entry, publicUrl, 'image.closed', 'success'));
17
188
  setEntry(null);
18
189
  setSrc('');
19
190
  setAlt('');
20
191
  setStatus('idle');
21
- }, []);
22
- const publicUrl = useMemo(() => {
23
- if (!entry?.storage?.key) {
24
- return src && /^https?:/i.test(src) ? src : '';
25
- }
26
- if (buildShareUrl)
27
- return buildShareUrl(entry.storage.key, shortId);
28
- if (typeof window === 'undefined')
29
- return '';
30
- if (shortId) {
31
- return `${window.location.origin}/i/${shortId}`;
32
- }
33
- if (encodeShareToken) {
34
- return `${window.location.origin}/i/${encodeShareToken(entry.storage.key)}`;
35
- }
36
- return src && /^https?:/i.test(src) ? src : '';
37
- }, [buildShareUrl, encodeShareToken, entry?.storage?.key, shortId, src]);
192
+ setStatusText('');
193
+ }, [buildTelemetryPayload, entry, publicUrl, trackTelemetry]);
38
194
  useEffect(() => {
39
195
  if (!entry?.storage?.key) {
40
196
  setShortId(null);
@@ -72,57 +228,200 @@ export const useRenderLightbox = (options = {}) => {
72
228
  cancelled = true;
73
229
  };
74
230
  }, [entry?.storage?.key, entry?.storage?.metaKey, entry?.title, name, resolveTitle, shareEndpoint, user]);
75
- const submitToGallery = useCallback(async () => {
76
- if (!entry?.storage?.bucket || !entry?.storage?.key) {
231
+ const submitToGallery = useCallback(async (targetEntry = entry, targetSrc = src) => {
232
+ if (!targetEntry?.storage?.bucket || !targetEntry?.storage?.key) {
77
233
  setStatus('error');
234
+ setStatusText('Image storage is missing. Gallery submission is blocked until this render has a real stored asset.');
78
235
  return;
79
236
  }
80
237
  setStatus('sending');
81
- const title = resolveTitle?.(entry) ?? entry.title ?? '';
82
- const s3Url = entry.storage.url || `s3://${entry.storage.bucket}/${entry.storage.key}`;
83
- const fullPrompt = entry.fullPrompt ?? entry.prompt ?? '';
238
+ setStatusText(Boolean(isAdminUser) ? 'Publishing to gallery…' : 'Proposing to gallery…');
239
+ const title = resolveTitle?.(targetEntry) ?? targetEntry.title ?? '';
240
+ const s3Url = `s3://${targetEntry.storage.bucket}/${targetEntry.storage.key}`;
241
+ const fullPrompt = resolveComposerEffectivePrompt({
242
+ promptCompilation: targetEntry.promptCompilation ?? targetEntry.snapshot?.promptCompilation ?? null,
243
+ fullPrompt: targetEntry.fullPrompt ?? targetEntry.snapshot?.fullPrompt ?? null,
244
+ prompt: targetEntry.prompt ?? targetEntry.snapshot?.prompt ?? null,
245
+ });
246
+ const targetPublicUrl = resolvePublicUrl(targetEntry, targetSrc);
84
247
  try {
85
248
  const response = await fetch(galleryEndpoint, {
86
249
  method: 'POST',
87
250
  headers: { 'Content-Type': 'application/json' },
88
251
  body: JSON.stringify({
89
252
  s3Url,
90
- bucket: entry.storage.bucket,
91
- key: entry.storage.key,
92
- metaKey: entry.storage.metaKey,
253
+ bucket: targetEntry.storage.bucket,
254
+ key: targetEntry.storage.key,
255
+ metaKey: targetEntry.storage.metaKey,
256
+ snapshotId: targetEntry.snapshotId || targetEntry.snapshot?.id || null,
93
257
  prompt: fullPrompt,
94
258
  title,
259
+ promptPackId: targetEntry.promptPackId || promptPackId || null,
260
+ mode: targetEntry.modeId || mode || null,
261
+ toolId: targetEntry.snapshot?.toolId || null,
262
+ operationId: targetEntry.operationId || targetEntry.snapshot?.operationId || null,
95
263
  direct: Boolean(isAdminUser),
96
264
  }),
97
265
  });
98
- if (!response.ok) {
99
- throw new Error('gallery submission failed');
266
+ const data = await response.json().catch(() => null);
267
+ if (!response.ok || !data?.success) {
268
+ throw new Error(data?.error || 'gallery submission failed');
269
+ }
270
+ if (isAdminUser && data?.published !== true) {
271
+ throw new Error(`gallery submission was not published (${String(data?.status || 'unknown')})`);
100
272
  }
101
273
  trackTelemetry?.('gallery_submission', {
102
274
  s3Url,
103
- bucket: entry.storage.bucket,
104
- key: entry.storage.key,
105
- metaKey: entry.storage.metaKey,
275
+ bucket: targetEntry.storage.bucket,
276
+ key: targetEntry.storage.key,
277
+ metaKey: targetEntry.storage.metaKey,
106
278
  prompt: fullPrompt,
107
279
  title,
108
280
  direct: Boolean(isAdminUser),
109
- publicUrl,
281
+ publicUrl: targetPublicUrl,
282
+ imageAction: buildImageActionRecord({
283
+ snapshot: targetEntry.snapshot ?? null,
284
+ imageRecord: targetEntry,
285
+ kind: isAdminUser ? 'gallery.published' : 'gallery.submitted',
286
+ status: 'success',
287
+ publicUrl: targetPublicUrl,
288
+ }),
110
289
  });
290
+ trackTelemetry?.(isAdminUser ? 'gallery_publish' : 'gallery_submit', buildTelemetryPayload({ direct: Boolean(isAdminUser) }, targetEntry, targetPublicUrl, isAdminUser ? 'gallery.published' : 'gallery.submitted', 'success'));
111
291
  setStatus('sent');
292
+ setStatusText(data?.status === 'published' || isAdminUser
293
+ ? 'Image published to gallery.'
294
+ : 'Image proposed to gallery for review.');
295
+ setEntry((prev) => {
296
+ if (!prev || prev.id !== targetEntry.id)
297
+ return prev;
298
+ const nextStatus = data?.status || (isAdminUser ? 'published' : 'pending');
299
+ return {
300
+ ...prev,
301
+ galleryStatus: nextStatus,
302
+ snapshot: prev.snapshot
303
+ ? {
304
+ ...prev.snapshot,
305
+ galleryStatus: nextStatus,
306
+ }
307
+ : prev.snapshot,
308
+ };
309
+ });
310
+ }
311
+ catch (error) {
312
+ trackTelemetry?.(isAdminUser ? 'gallery_publish_error' : 'gallery_submit_error', buildTelemetryPayload({}, targetEntry, targetPublicUrl, isAdminUser ? 'gallery.published' : 'gallery.submitted', 'error'));
313
+ setStatus('error');
314
+ setStatusText(error?.message || 'Gallery submission failed.');
315
+ }
316
+ }, [buildTelemetryPayload, entry, galleryEndpoint, isAdminUser, mode, promptPackId, resolvePublicUrl, resolveTitle, src, trackTelemetry]);
317
+ const shareImage = useCallback(async (targetEntry = entry, targetSrc = src) => {
318
+ const targetPublicUrl = resolvePublicUrl(targetEntry, targetSrc);
319
+ if (!targetPublicUrl)
320
+ return;
321
+ try {
322
+ if (typeof navigator !== 'undefined' && typeof navigator.share === 'function') {
323
+ await navigator.share({
324
+ title: targetEntry?.title || 'render image',
325
+ text: mode || 'Composer render',
326
+ url: targetPublicUrl,
327
+ });
328
+ }
329
+ else {
330
+ await navigator.clipboard.writeText(targetPublicUrl);
331
+ }
332
+ trackTelemetry?.('snapshot_share', buildTelemetryPayload({}, targetEntry, targetPublicUrl, 'image.shared', 'success'));
112
333
  }
113
334
  catch {
335
+ trackTelemetry?.('snapshot_share_error', buildTelemetryPayload({}, targetEntry, targetPublicUrl, 'image.shared', 'error'));
336
+ }
337
+ }, [buildTelemetryPayload, entry, mode, resolvePublicUrl, src, trackTelemetry]);
338
+ const copyPrompt = useCallback(async (targetEntry = entry) => {
339
+ const targetPublicUrl = resolvePublicUrl(targetEntry);
340
+ await copyText(resolveComposerEffectivePrompt({
341
+ promptCompilation: targetEntry?.promptCompilation ?? targetEntry?.snapshot?.promptCompilation ?? null,
342
+ fullPrompt: targetEntry?.fullPrompt ?? targetEntry?.snapshot?.fullPrompt ?? null,
343
+ prompt: targetEntry?.prompt ?? targetEntry?.snapshot?.prompt ?? null,
344
+ }), 'snapshot_copy_prompt', targetEntry, targetPublicUrl);
345
+ }, [copyText, entry, resolvePublicUrl]);
346
+ const copyPublicLink = useCallback(async (targetEntry = entry, targetSrc = src) => {
347
+ const ensuredShortId = await ensureShortlink(targetEntry);
348
+ const targetPublicUrl = ensuredShortId
349
+ ? resolveComposerImageLinks({
350
+ storage: targetEntry?.storage ?? targetEntry?.snapshot?.storage ?? null,
351
+ shortId: ensuredShortId,
352
+ origin: getOrigin(),
353
+ fallbackImageUrl: targetSrc,
354
+ }).pageUrl
355
+ : resolvePublicUrl(targetEntry, targetSrc);
356
+ await copyText(targetPublicUrl, 'snapshot_copy_public_link', targetEntry, targetPublicUrl);
357
+ }, [copyText, ensureShortlink, entry, getOrigin, resolvePublicUrl, src]);
358
+ const copyImageLink = useCallback(async (targetEntry = entry, targetSrc = src) => {
359
+ const ensuredShortId = await ensureShortlink(targetEntry);
360
+ const imageUrl = ensuredShortId
361
+ ? resolveComposerImageLinks({
362
+ storage: targetEntry?.storage ?? targetEntry?.snapshot?.storage ?? null,
363
+ shortId: ensuredShortId,
364
+ origin: getOrigin(),
365
+ fallbackImageUrl: targetSrc,
366
+ }).directImageUrl
367
+ : resolveAssetUrl(targetEntry, targetSrc);
368
+ await copyText(imageUrl, 'snapshot_copy_image_link', targetEntry);
369
+ }, [copyText, ensureShortlink, entry, getOrigin, resolveAssetUrl, src]);
370
+ const downloadImage = useCallback((targetEntry = entry, targetSrc = src) => {
371
+ const targetPublicUrl = resolvePublicUrl(targetEntry, targetSrc);
372
+ const href = resolveAssetUrl(targetEntry, targetSrc);
373
+ if (!href || typeof document === 'undefined')
374
+ return;
375
+ const link = document.createElement('a');
376
+ link.href = href;
377
+ link.download = `${(targetEntry?.title || 'render-image').replace(/\s+/g, '-').toLowerCase()}.png`;
378
+ document.body.appendChild(link);
379
+ link.click();
380
+ link.remove();
381
+ trackTelemetry?.('snapshot_download', buildTelemetryPayload({}, targetEntry, targetPublicUrl, 'image.downloaded', 'success'));
382
+ }, [buildTelemetryPayload, entry, resolveAssetUrl, resolvePublicUrl, src, trackTelemetry]);
383
+ const restartFromSnapshot = useCallback(async (targetEntry = entry) => {
384
+ if (!targetEntry)
385
+ return;
386
+ const targetPublicUrl = resolvePublicUrl(targetEntry);
387
+ let snapshot = buildEntrySnapshot(targetEntry);
388
+ if (!isRestartableGallerySnapshot(snapshot) && targetEntry.storage?.metaKey) {
389
+ try {
390
+ const response = await fetch(`/api/images/meta?key=${encodeURIComponent(targetEntry.storage.metaKey)}`);
391
+ const data = await response.json().catch(() => null);
392
+ if (response.ok && data?.success && data?.meta?.snapshot) {
393
+ snapshot = data.meta.snapshot;
394
+ }
395
+ }
396
+ catch {
397
+ // Restart failure is reported below through status and telemetry.
398
+ }
399
+ }
400
+ if (!isRestartableGallerySnapshot(snapshot)) {
114
401
  setStatus('error');
402
+ setStatusText('Saved snapshot is incomplete. Restart is blocked until this image has full durable snapshot metadata.');
403
+ trackTelemetry?.('snapshot_restart_error', buildTelemetryPayload({}, targetEntry, targetPublicUrl, 'snapshot.restarted', 'error'));
404
+ return;
115
405
  }
116
- }, [entry, galleryEndpoint, isAdminUser, publicUrl, resolveTitle, trackTelemetry]);
406
+ onRestartFromSnapshot?.(targetEntry, snapshot);
407
+ trackTelemetry?.('snapshot_restart', buildTelemetryPayload({}, targetEntry, targetPublicUrl, 'snapshot.restarted', 'success'));
408
+ }, [buildEntrySnapshot, buildTelemetryPayload, entry, onRestartFromSnapshot, resolvePublicUrl, trackTelemetry]);
117
409
  return {
118
410
  entry,
119
411
  src,
120
412
  alt,
121
413
  status,
414
+ statusText,
122
415
  shortId,
123
416
  publicUrl,
124
417
  open,
125
418
  close,
419
+ shareImage,
420
+ copyPrompt,
421
+ copyPublicLink,
422
+ copyImageLink,
423
+ downloadImage,
424
+ restartFromSnapshot,
126
425
  submitToGallery,
127
426
  };
128
427
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@treasuryspatial/prompt-ui-kit",
3
- "version": "0.1.14",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
- "license": "UNLICENSED",
5
+ "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
@@ -18,15 +18,17 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@treasuryspatial/render-kit": "^0.1.17"
21
+ "@treasuryspatial/render-kit": "^0.1.23",
22
+ "styled-components": "^6.1.19"
22
23
  },
23
24
  "peerDependencies": {
24
25
  "react": ">=18"
25
26
  },
26
27
  "devDependencies": {
27
- "react": "19.1.0",
28
28
  "@types/react": "^19",
29
- "@types/react-dom": "^19"
29
+ "@types/react-dom": "^19",
30
+ "@types/styled-components": "^5.1.34",
31
+ "react": "19.1.0"
30
32
  },
31
33
  "scripts": {
32
34
  "build": "tsc -b",