@widgetic/creator 0.3.67 → 0.3.68
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/README.md +52 -98
- package/dist/CreatorApp.svelte +436 -146
- package/dist/components/WidgetDetails.svelte +32 -3
- package/dist/components/WidgetDetails.svelte.d.ts +2 -0
- package/dist/components/WidgetShapeLivePreviews.svelte +133 -46
- package/dist/components/WidgetShapeLivePreviews.svelte.d.ts +2 -0
- package/dist/stores/websocketStore.d.ts +1 -1
- package/dist/stores/websocketStore.js +1 -1
- package/dist/utils/freezeWatchdog.js +38 -7
- package/package.json +4 -5
package/dist/CreatorApp.svelte
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
} from '@widgetic/api-sdk';
|
|
30
30
|
import type {
|
|
31
31
|
Middleware,
|
|
32
|
-
UpdateComposition,
|
|
33
32
|
GenerateCodeRequest,
|
|
34
33
|
AgentModelsGet200ResponseModelsInner
|
|
35
34
|
} from '@widgetic/api-sdk';
|
|
@@ -500,6 +499,7 @@
|
|
|
500
499
|
|
|
501
500
|
// Chat module for code generation conversation UI
|
|
502
501
|
import { Chat, ContextType } from '@widgetic/chat';
|
|
502
|
+
import type { ClarificationSuggestion } from '@widgetic/chat';
|
|
503
503
|
|
|
504
504
|
// Eagerly initiate canvas chunk fetching at script evaluation (parallel with DOM parsing)
|
|
505
505
|
const canvasModulePromise = typeof window !== 'undefined' ? import('@widgetic/canvas') : null;
|
|
@@ -535,6 +535,7 @@
|
|
|
535
535
|
}
|
|
536
536
|
installLongTaskFreezeReporter();
|
|
537
537
|
installFreezeWatchdog();
|
|
538
|
+
console.info('[FreezeWatchdog] Freeze reports log on this page. In DevTools, pick the top frame — not a widget iframe.');
|
|
538
539
|
});
|
|
539
540
|
|
|
540
541
|
/**
|
|
@@ -871,6 +872,21 @@
|
|
|
871
872
|
function isAnyWidgetGenerating(): boolean {
|
|
872
873
|
return isGeneratingCode || Object.keys(generatingWidgetIds).some((id) => generatingWidgetIds[id]);
|
|
873
874
|
}
|
|
875
|
+
|
|
876
|
+
/** Synchronous hold so a screenshot cannot postMessage after codegen has started. */
|
|
877
|
+
function noteCodegenActive(active: boolean): void {
|
|
878
|
+
if (typeof window === 'undefined') return;
|
|
879
|
+
const holder = window as Window & { __widgeticCodegenCount?: number };
|
|
880
|
+
holder.__widgeticCodegenCount = Math.max(0, (holder.__widgeticCodegenCount || 0) + (active ? 1 : -1));
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function codegenCaptureBlocked(): boolean {
|
|
884
|
+
if (typeof window !== 'undefined' && ((window as Window & { __widgeticCodegenCount?: number }).__widgeticCodegenCount || 0) > 0) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (isAnyWidgetGenerating()) return true;
|
|
888
|
+
return /generating code|saving to repository/i.test(generateCodeStatus || '');
|
|
889
|
+
}
|
|
874
890
|
const widgetAbortControllers = new Map<string, AbortController>();
|
|
875
891
|
let currentGenerationOperationId: string | null = null;
|
|
876
892
|
let generateCodeStatus: string | null = null;
|
|
@@ -905,7 +921,7 @@
|
|
|
905
921
|
// Chat component reference and context (focused panel)
|
|
906
922
|
let chatMethods: {
|
|
907
923
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
908
|
-
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?:
|
|
924
|
+
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: ClarificationSuggestion[]) => void;
|
|
909
925
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
910
926
|
getConversationId: () => string | null;
|
|
911
927
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -1041,7 +1057,7 @@
|
|
|
1041
1057
|
fileSize?: number;
|
|
1042
1058
|
}>,
|
|
1043
1059
|
sourceWidgetId?: string,
|
|
1044
|
-
sendOptions?: { skipIntentGate?: boolean },
|
|
1060
|
+
sendOptions?: { skipIntentGate?: boolean; chipIntent?: ClarificationSuggestion['intent'] },
|
|
1045
1061
|
) {
|
|
1046
1062
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1047
1063
|
if (!widgetId) return;
|
|
@@ -1165,6 +1181,7 @@
|
|
|
1165
1181
|
promptImagesOverride: effectivePromptImages.length > 0 ? effectivePromptImages : undefined,
|
|
1166
1182
|
persistFileAttachments: persistFileAttachments.length > 0 ? persistFileAttachments : undefined,
|
|
1167
1183
|
skipIntentGate: sendOptions?.skipIntentGate === true,
|
|
1184
|
+
chipIntent: sendOptions?.chipIntent,
|
|
1168
1185
|
});
|
|
1169
1186
|
}
|
|
1170
1187
|
|
|
@@ -1626,6 +1643,7 @@
|
|
|
1626
1643
|
buildUserPromptMessageContent(prompt),
|
|
1627
1644
|
'user',
|
|
1628
1645
|
persistAttachments.length > 0 ? persistAttachments : undefined,
|
|
1646
|
+
undefined,
|
|
1629
1647
|
widgetId,
|
|
1630
1648
|
);
|
|
1631
1649
|
const savedAtt = saved?.attachments || saved?.data?.attachments || [];
|
|
@@ -1726,32 +1744,46 @@
|
|
|
1726
1744
|
iframe: HTMLIFrameElement,
|
|
1727
1745
|
timeoutMs = 6000,
|
|
1728
1746
|
): Promise<{ data: string | null; error: string | null }> {
|
|
1729
|
-
if (
|
|
1747
|
+
if (codegenCaptureBlocked()) {
|
|
1730
1748
|
return Promise.resolve({ data: null, error: 'generation in progress' });
|
|
1731
1749
|
}
|
|
1750
|
+
const captureEpoch = screenshotGenerationEpoch;
|
|
1732
1751
|
return new Promise((resolve) => {
|
|
1733
1752
|
if (!iframe.contentWindow) {
|
|
1734
1753
|
resolve({ data: null, error: 'no-content-window' });
|
|
1735
1754
|
return;
|
|
1736
1755
|
}
|
|
1737
|
-
const
|
|
1756
|
+
const finish = (result: { data: string | null; error: string | null }) => {
|
|
1738
1757
|
window.removeEventListener('message', handler);
|
|
1739
|
-
|
|
1758
|
+
clearTimeout(timeout);
|
|
1759
|
+
if (captureEpoch !== screenshotGenerationEpoch || isAnyWidgetGenerating()) {
|
|
1760
|
+
resolve({ data: null, error: 'generation in progress' });
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
resolve(result);
|
|
1764
|
+
};
|
|
1765
|
+
const timeout = setTimeout(() => {
|
|
1766
|
+
finish({ data: null, error: 'timeout' });
|
|
1740
1767
|
}, timeoutMs);
|
|
1741
1768
|
|
|
1742
1769
|
function handler(event: MessageEvent) {
|
|
1743
1770
|
if (event.data?.type !== 'widgetic:screenshot') return;
|
|
1744
|
-
// Ignore other widgets and synthetic replies with no source window.
|
|
1745
1771
|
if (event.source !== iframe.contentWindow) return;
|
|
1746
|
-
|
|
1747
|
-
window.removeEventListener('message', handler);
|
|
1748
|
-
resolve({
|
|
1772
|
+
finish({
|
|
1749
1773
|
data: typeof event.data.data === 'string' ? event.data.data : null,
|
|
1750
1774
|
error: typeof event.data.error === 'string' ? event.data.error : null,
|
|
1751
1775
|
});
|
|
1752
1776
|
}
|
|
1753
1777
|
window.addEventListener('message', handler);
|
|
1754
|
-
|
|
1778
|
+
try {
|
|
1779
|
+
if (codegenCaptureBlocked()) {
|
|
1780
|
+
finish({ data: null, error: 'generation in progress' });
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
iframe.contentWindow.postMessage({ type: 'widgetic:request-screenshot' }, '*');
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
finish({ data: null, error: err instanceof Error ? err.message : 'postMessage failed' });
|
|
1786
|
+
}
|
|
1755
1787
|
});
|
|
1756
1788
|
}
|
|
1757
1789
|
|
|
@@ -1764,7 +1796,7 @@
|
|
|
1764
1796
|
// startup on the main thread this page shares with it, and the capture that
|
|
1765
1797
|
// follows is refused during a generation anyway (`requestIframeScreenshot`),
|
|
1766
1798
|
// so the whole path is skipped rather than paying the load for nothing.
|
|
1767
|
-
if (
|
|
1799
|
+
if (codegenCaptureBlocked()) {
|
|
1768
1800
|
console.log('[Chat→Screenshot] Skip hidden Worker capture — codegen in progress', widgetId?.substring(0, 8));
|
|
1769
1801
|
return null;
|
|
1770
1802
|
}
|
|
@@ -1816,14 +1848,14 @@
|
|
|
1816
1848
|
|
|
1817
1849
|
/** Explicit Refresh only: capture the loaded frame without opening a panel or publishing. */
|
|
1818
1850
|
async function handleWidgetShapeRefresh(widgetId, iframe, isCurrentFrame) {
|
|
1819
|
-
if (
|
|
1851
|
+
if (codegenCaptureBlocked()) return;
|
|
1820
1852
|
const capturedCanvasId = getEffectiveCanvasId();
|
|
1821
1853
|
const capturedCanvas = canvasRef;
|
|
1822
1854
|
const capturedSource = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
1823
1855
|
// Frame/intent identity owns this capture, not focused-panel HEAD hydration
|
|
1824
1856
|
// or another widget's global generation operation.
|
|
1825
1857
|
const isCurrent = () => isCurrentFrame() && !!getWidgetById(widgetId)
|
|
1826
|
-
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !
|
|
1858
|
+
&& !isDraftConvertId(widgetId) && !generatingWidgetIds[widgetId] && !codegenCaptureBlocked()
|
|
1827
1859
|
&& capturedCanvasId === getEffectiveCanvasId() && capturedCanvas === canvasRef
|
|
1828
1860
|
&& capturedSource === (shapePreviewSourceByWidgetId[widgetId] || 'live')
|
|
1829
1861
|
&& resolveLiveWidgetShapeIframe(widgetId) === iframe;
|
|
@@ -1854,7 +1886,7 @@
|
|
|
1854
1886
|
}
|
|
1855
1887
|
// Never run background / refresh screenshot captures while code generation is running —
|
|
1856
1888
|
// html2canvas inside any same-origin iframe freezes the browser main thread.
|
|
1857
|
-
if ((
|
|
1889
|
+
if (codegenCaptureBlocked()) {
|
|
1858
1890
|
console.log('[Chat→Screenshot] Skip capture — generation in progress');
|
|
1859
1891
|
return null;
|
|
1860
1892
|
}
|
|
@@ -1882,6 +1914,10 @@
|
|
|
1882
1914
|
|
|
1883
1915
|
// 1) Always try the visible live iframe first (published HTML from Worker includes screenshot script)
|
|
1884
1916
|
if (liveIframe?.contentWindow) {
|
|
1917
|
+
if (codegenCaptureBlocked()) {
|
|
1918
|
+
console.log('[Chat→Screenshot] Skip live capture — codegen in progress', targetId.substring(0, 8));
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1885
1921
|
console.log('[Chat→Screenshot] Live WidgetShape iframe', {
|
|
1886
1922
|
targetId: targetId.substring(0, 8),
|
|
1887
1923
|
isWorker: isWorkerPreviewUrl(liveSrc),
|
|
@@ -2050,7 +2086,14 @@
|
|
|
2050
2086
|
messageContent: m.message_content || m.messageContent || '',
|
|
2051
2087
|
messageType: m.message_type || m.messageType || 'user',
|
|
2052
2088
|
attachments,
|
|
2089
|
+
// Suggestion chips from a persisted clarification card — restored
|
|
2090
|
+
// on reload exactly as they were served.
|
|
2091
|
+
suggestions: Array.isArray(m.suggestions) && m.suggestions.length > 0 ? m.suggestions : undefined,
|
|
2053
2092
|
isCommit: m.is_commit || m.isCommit || false,
|
|
2093
|
+
// What the turn shipped, in the commit's own words — persisted beside
|
|
2094
|
+
// is_commit by the codegen pipeline and rendered on the turn's commit
|
|
2095
|
+
// chrome (the surface that survives a reload).
|
|
2096
|
+
commitTitle: m.commit_title || m.commitTitle || undefined,
|
|
2054
2097
|
userId: m.user_id || m.userId || '',
|
|
2055
2098
|
createdAt: new Date(m.created_at || m.createdAt),
|
|
2056
2099
|
updatedAt: new Date(m.updated_at || m.updatedAt || m.created_at || m.createdAt),
|
|
@@ -2223,6 +2266,7 @@
|
|
|
2223
2266
|
file_size?: number | null;
|
|
2224
2267
|
display_order?: number;
|
|
2225
2268
|
}>,
|
|
2269
|
+
suggestions?: ClarificationSuggestion[],
|
|
2226
2270
|
persistWidgetId?: string | null,
|
|
2227
2271
|
): Promise<any> {
|
|
2228
2272
|
try {
|
|
@@ -2258,25 +2302,39 @@
|
|
|
2258
2302
|
user_id: userId,
|
|
2259
2303
|
is_commit: false,
|
|
2260
2304
|
attachments: apiAttachments.length > 0 ? apiAttachments : undefined,
|
|
2305
|
+
// Clarification-card suggestions ride the same write as the reply
|
|
2306
|
+
// they belong to, so a reload re-renders the card with its chips.
|
|
2307
|
+
...(suggestions && suggestions.length > 0 ? { suggestions } : {}),
|
|
2261
2308
|
};
|
|
2262
|
-
|
|
2309
|
+
const persistMessage = async (payload: typeof createPayload) => {
|
|
2263
2310
|
const response = await messagesClient.createMessageRaw({
|
|
2264
2311
|
conversationId,
|
|
2265
|
-
createMessageRequest:
|
|
2312
|
+
createMessageRequest: payload,
|
|
2266
2313
|
});
|
|
2267
2314
|
const json = await response.raw.json();
|
|
2268
|
-
// console.log('[Chat→Backend] Saved message to conversation:', conversationId);
|
|
2269
2315
|
const saved = json?.data || json;
|
|
2270
2316
|
const savedAtt = saved?.attachments;
|
|
2271
2317
|
return {
|
|
2272
2318
|
...saved,
|
|
2273
|
-
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt :
|
|
2319
|
+
attachments: (Array.isArray(savedAtt) && savedAtt.length > 0) ? savedAtt : (payload.attachments || []),
|
|
2274
2320
|
};
|
|
2321
|
+
};
|
|
2322
|
+
try {
|
|
2323
|
+
return await persistMessage(createPayload);
|
|
2275
2324
|
} catch (withAttachmentsError) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2325
|
+
if (apiAttachments.length > 0) {
|
|
2326
|
+
console.warn(
|
|
2327
|
+
'[Chat-Backend] Save with attachments failed — retrying text-only so the turn is not lost:',
|
|
2328
|
+
withAttachmentsError,
|
|
2329
|
+
);
|
|
2330
|
+
try {
|
|
2331
|
+
return await persistMessage({ ...createPayload, attachments: undefined });
|
|
2332
|
+
} catch (textOnlyError) {
|
|
2333
|
+
console.error('[Chat-Backend] Text-only message save also failed:', textOnlyError);
|
|
2334
|
+
throw textOnlyError;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
console.error('[Chat-Backend] Message save failed:', withAttachmentsError);
|
|
2280
2338
|
throw withAttachmentsError;
|
|
2281
2339
|
}
|
|
2282
2340
|
} catch (error) {
|
|
@@ -2317,34 +2375,47 @@
|
|
|
2317
2375
|
null;
|
|
2318
2376
|
// console.log('[Chat→Backend] Git rollback result:', gitRollback, { undoMode, targetCommit, panelWidgetId });
|
|
2319
2377
|
if (gitRollback?.success) {
|
|
2320
|
-
//
|
|
2321
|
-
// artifact —
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2378
|
+
// Restore changes Git HEAD (often a new rollback commit). Never reuse a
|
|
2379
|
+
// published CDN artifact — that is Switch Version, and it hid the rebuild.
|
|
2380
|
+
const rollbackHeadSha =
|
|
2381
|
+
gitRollback.commitId ?? gitRollback.commit_id ?? targetCommit ?? null;
|
|
2382
|
+
const historicalSha = targetCommit || rollbackHeadSha;
|
|
2383
|
+
if (panelWidgetId) {
|
|
2384
|
+
if (rollbackHeadSha) {
|
|
2385
|
+
lastCommitId = rollbackHeadSha;
|
|
2386
|
+
saveCommitIdForWidget(panelWidgetId, rollbackHeadSha);
|
|
2387
|
+
patchPanelSnapshot(panelWidgetId, { lastCommitId: rollbackHeadSha });
|
|
2388
|
+
patchCodegenUiState(panelWidgetId, { lastCommitId: rollbackHeadSha });
|
|
2389
|
+
}
|
|
2390
|
+
const targetSha = String(historicalSha || '').toLowerCase();
|
|
2391
|
+
const matchedVersion = (resolveWidgetShapeVersionOptions(panelWidgetId) ?? [])
|
|
2392
|
+
.find((option) => {
|
|
2393
|
+
const optionSha = String(option.commitSha ?? '').toLowerCase();
|
|
2394
|
+
return optionSha.length >= 7 && targetSha.length >= 7
|
|
2395
|
+
&& (optionSha === targetSha || optionSha.startsWith(targetSha) || targetSha.startsWith(optionSha));
|
|
2396
|
+
});
|
|
2397
|
+
if (matchedVersion?.id?.startsWith('v:')) {
|
|
2398
|
+
const restoredVersion = parseInt(matchedVersion.id.slice(2), 10);
|
|
2399
|
+
if (Number.isFinite(restoredVersion)) {
|
|
2400
|
+
restoredViewingVersionByWidgetId = {
|
|
2401
|
+
...restoredViewingVersionByWidgetId,
|
|
2402
|
+
[panelWidgetId]: restoredVersion,
|
|
2403
|
+
};
|
|
2404
|
+
widgetShapeLivePreviewsRef?.setSelectedVersion?.(panelWidgetId, `v:${restoredVersion}`);
|
|
2341
2405
|
}
|
|
2342
2406
|
}
|
|
2407
|
+
shapePreviewSourceByWidgetId = {
|
|
2408
|
+
...shapePreviewSourceByWidgetId,
|
|
2409
|
+
[panelWidgetId]: 'preview',
|
|
2410
|
+
};
|
|
2411
|
+
getWidgetDetailsRef(panelWidgetId)?.clearCodegenPreviewFresh?.();
|
|
2412
|
+
syncWidgetShapeLivePreviews();
|
|
2413
|
+
panelDisplayRevision += 1;
|
|
2343
2414
|
}
|
|
2344
2415
|
showToast('success', undoMode ? 'Change undone' : 'Checkpoint restored', {
|
|
2345
2416
|
description: undoMode
|
|
2346
|
-
? `Code reverted to commit ${formatCommitShaShort(
|
|
2347
|
-
: `Widget code at commit ${formatCommitShaShort(
|
|
2417
|
+
? `Code reverted to commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`
|
|
2418
|
+
: `Widget code at commit ${formatCommitShaShort(historicalSha)}. Refreshing preview…`,
|
|
2348
2419
|
duration: 6000,
|
|
2349
2420
|
});
|
|
2350
2421
|
liveDesignValues = {};
|
|
@@ -2353,18 +2424,12 @@
|
|
|
2353
2424
|
compositionDesignSchema = null;
|
|
2354
2425
|
compositionContentSchema = null;
|
|
2355
2426
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
2356
|
-
_isRestoringCheckpoint =
|
|
2357
|
-
if (previewFromVersionCache) {
|
|
2358
|
-
// Immutable artifact already serves the preview; no rebuild ladder.
|
|
2359
|
-
syncWidgetShapeLivePreviews();
|
|
2360
|
-
setTimeout(() => handleRefreshScreenshot(panelWidgetId, { quiet: true }), 2000);
|
|
2361
|
-
return { ...data, previewFromVersionCache };
|
|
2362
|
-
}
|
|
2427
|
+
_isRestoringCheckpoint = true;
|
|
2363
2428
|
const panelRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : widgetDetails;
|
|
2364
2429
|
const scheduleBuildRetry = async () => {
|
|
2365
|
-
const delays = [2500, 5000, 8000];
|
|
2430
|
+
const delays = [400, 2500, 5000, 8000];
|
|
2366
2431
|
for (let i = 0; i < delays.length; i++) {
|
|
2367
|
-
await new Promise((r) => setTimeout(r, delays[i]));
|
|
2432
|
+
if (i > 0) await new Promise((r) => setTimeout(r, delays[i]));
|
|
2368
2433
|
if (i === 0) suppressNextBuildFromRepoChatMessage = true;
|
|
2369
2434
|
try {
|
|
2370
2435
|
const activeRef = panelWidgetId ? getWidgetDetailsRef(panelWidgetId) : panelRef;
|
|
@@ -2378,7 +2443,9 @@
|
|
|
2378
2443
|
[panelWidgetId]: 'preview',
|
|
2379
2444
|
};
|
|
2380
2445
|
syncWidgetShapeLivePreviews();
|
|
2381
|
-
|
|
2446
|
+
widgetShapeLivePreviewsRef?.requestReload?.(panelWidgetId);
|
|
2447
|
+
panelDisplayRevision += 1;
|
|
2448
|
+
scheduleIdleThumbnailPersist(panelWidgetId);
|
|
2382
2449
|
}
|
|
2383
2450
|
return;
|
|
2384
2451
|
}
|
|
@@ -3938,9 +4005,7 @@
|
|
|
3938
4005
|
};
|
|
3939
4006
|
await compositionsClient.updateComposition({
|
|
3940
4007
|
id: selectedComposition.id,
|
|
3941
|
-
|
|
3942
|
-
// the generated UpdateComposition model predates that field.
|
|
3943
|
-
updateComposition: { embedSettings } as unknown as UpdateComposition
|
|
4008
|
+
updateComposition: { embedSettings }
|
|
3944
4009
|
});
|
|
3945
4010
|
selectedComposition = { ...selectedComposition, embedSettings };
|
|
3946
4011
|
embedDimensionsChanged = false;
|
|
@@ -4079,6 +4144,11 @@
|
|
|
4079
4144
|
|
|
4080
4145
|
// debugMode — current ON/OFF state. User can still toggle when the UI is available.
|
|
4081
4146
|
let debugMode = debugModeAvailable;
|
|
4147
|
+
if (typeof localStorage !== 'undefined' && debugModeAvailable) {
|
|
4148
|
+
const storedDebugMode = localStorage.getItem('widgetic:site-debug-on');
|
|
4149
|
+
if (storedDebugMode === '1' || storedDebugMode === 'true') debugMode = true;
|
|
4150
|
+
if (storedDebugMode === '0' || storedDebugMode === 'false') debugMode = false;
|
|
4151
|
+
}
|
|
4082
4152
|
// console.log('[WidgetCreator] Debug from host/env', {
|
|
4083
4153
|
// hostProvidedDebug,
|
|
4084
4154
|
// initialDebugMode,
|
|
@@ -4263,6 +4333,11 @@
|
|
|
4263
4333
|
function toggleDebugMode() {
|
|
4264
4334
|
if (!debugModeAvailable) return;
|
|
4265
4335
|
debugMode = !debugMode;
|
|
4336
|
+
try {
|
|
4337
|
+
localStorage.setItem('widgetic:site-debug-on', debugMode ? '1' : '0');
|
|
4338
|
+
} catch (err) {
|
|
4339
|
+
console.warn('[WidgetCreator] Could not persist debug mode', err);
|
|
4340
|
+
}
|
|
4266
4341
|
// When turning OFF: switches to default model (from API config), mock console
|
|
4267
4342
|
// When turning ON: enables model selector, real-time console
|
|
4268
4343
|
const effectiveModel = debugMode ? userSelectedModelId : defaultModelId;
|
|
@@ -4461,9 +4536,8 @@
|
|
|
4461
4536
|
const anyW = widget as { thumbnailUrl?: string | null; thumbnail_url?: string | null } | null;
|
|
4462
4537
|
const hasThumb = !!(anyW?.thumbnailUrl || anyW?.thumbnail_url);
|
|
4463
4538
|
if (!hasThumb) {
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
}, 1200);
|
|
4539
|
+
// A capture 1s after open runs html2canvas on the shared main thread.
|
|
4540
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
4467
4541
|
}
|
|
4468
4542
|
return true;
|
|
4469
4543
|
}
|
|
@@ -7740,11 +7814,18 @@
|
|
|
7740
7814
|
const ctx = canvas.getContext('2d');
|
|
7741
7815
|
if (!ctx) return dataUrl;
|
|
7742
7816
|
ctx.drawImage(img, 0, 0, width, height);
|
|
7817
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
7818
|
+
const encode = (quality: number) =>
|
|
7819
|
+
new Promise<string>((resolve) => {
|
|
7820
|
+
setTimeout(() => {
|
|
7821
|
+
resolve(canvas.toDataURL('image/jpeg', quality));
|
|
7822
|
+
}, 0);
|
|
7823
|
+
});
|
|
7743
7824
|
let quality = 0.82;
|
|
7744
|
-
let out =
|
|
7825
|
+
let out = await encode(quality);
|
|
7745
7826
|
while (out.length > maxPayloadChars && quality > 0.45) {
|
|
7746
7827
|
quality -= 0.08;
|
|
7747
|
-
out =
|
|
7828
|
+
out = await encode(quality);
|
|
7748
7829
|
}
|
|
7749
7830
|
// console.log('[WidgetCreator] Compressed reference image for codegen', {
|
|
7750
7831
|
// fromChars: dataUrl.length,
|
|
@@ -7823,6 +7904,7 @@
|
|
|
7823
7904
|
*/
|
|
7824
7905
|
/** Debounced quiet sync: live Worker preview → canvas WidgetShape thumbnail. */
|
|
7825
7906
|
const canvasShapePreviewSyncTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7907
|
+
const quietScreenshotTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7826
7908
|
function cancelCanvasShapePreviewSync(widgetId?: string | null): void {
|
|
7827
7909
|
if (widgetId) {
|
|
7828
7910
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
@@ -7833,13 +7915,86 @@
|
|
|
7833
7915
|
for (const timer of canvasShapePreviewSyncTimers.values()) clearTimeout(timer);
|
|
7834
7916
|
canvasShapePreviewSyncTimers.clear();
|
|
7835
7917
|
}
|
|
7918
|
+
function cancelQuietScreenshots(widgetId?: string | null): void {
|
|
7919
|
+
if (widgetId) {
|
|
7920
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7921
|
+
if (existing) clearTimeout(existing);
|
|
7922
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7923
|
+
cancelCanvasShapePreviewSync(widgetId);
|
|
7924
|
+
return;
|
|
7925
|
+
}
|
|
7926
|
+
for (const timer of quietScreenshotTimers.values()) clearTimeout(timer);
|
|
7927
|
+
quietScreenshotTimers.clear();
|
|
7928
|
+
cancelCanvasShapePreviewSync();
|
|
7929
|
+
}
|
|
7930
|
+
function scheduleQuietScreenshot(widgetId: string, delayMs = 2000): void {
|
|
7931
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7932
|
+
const existing = quietScreenshotTimers.get(widgetId);
|
|
7933
|
+
if (existing) clearTimeout(existing);
|
|
7934
|
+
const timer = setTimeout(() => {
|
|
7935
|
+
quietScreenshotTimers.delete(widgetId);
|
|
7936
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7937
|
+
void handleRefreshScreenshot(widgetId, { quiet: true });
|
|
7938
|
+
}, delayMs);
|
|
7939
|
+
quietScreenshotTimers.set(widgetId, timer);
|
|
7940
|
+
}
|
|
7941
|
+
/**
|
|
7942
|
+
* Latest widget capture for the next LLM turn, without freezing a generation.
|
|
7943
|
+
* html2canvas shares the page main thread, so this waits until codegen has
|
|
7944
|
+
* been idle, then persists thumbnail_url only (no canvas JSON serialize).
|
|
7945
|
+
*/
|
|
7946
|
+
const idleThumbnailTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
7947
|
+
const IDLE_THUMBNAIL_DELAY_MS = 12000;
|
|
7948
|
+
function cancelIdleThumbnailPersist(widgetId?: string | null): void {
|
|
7949
|
+
if (widgetId) {
|
|
7950
|
+
const existing = idleThumbnailTimers.get(widgetId);
|
|
7951
|
+
if (existing) clearTimeout(existing);
|
|
7952
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7953
|
+
return;
|
|
7954
|
+
}
|
|
7955
|
+
for (const timer of idleThumbnailTimers.values()) clearTimeout(timer);
|
|
7956
|
+
idleThumbnailTimers.clear();
|
|
7957
|
+
}
|
|
7958
|
+
function scheduleIdleThumbnailPersist(widgetId: string): void {
|
|
7959
|
+
if (!widgetId) return;
|
|
7960
|
+
cancelIdleThumbnailPersist(widgetId);
|
|
7961
|
+
const timer = setTimeout(() => {
|
|
7962
|
+
idleThumbnailTimers.delete(widgetId);
|
|
7963
|
+
const run = () => {
|
|
7964
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7965
|
+
scheduleIdleThumbnailPersist(widgetId);
|
|
7966
|
+
return;
|
|
7967
|
+
}
|
|
7968
|
+
void persistIdleWidgetThumbnail(widgetId);
|
|
7969
|
+
};
|
|
7970
|
+
if (typeof requestIdleCallback === 'function') {
|
|
7971
|
+
requestIdleCallback(() => run(), { timeout: 8000 });
|
|
7972
|
+
} else {
|
|
7973
|
+
run();
|
|
7974
|
+
}
|
|
7975
|
+
}, IDLE_THUMBNAIL_DELAY_MS);
|
|
7976
|
+
idleThumbnailTimers.set(widgetId, timer);
|
|
7977
|
+
}
|
|
7978
|
+
async function persistIdleWidgetThumbnail(widgetId: string): Promise<void> {
|
|
7979
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7980
|
+
const iframe = resolveLiveWidgetShapeIframe(widgetId);
|
|
7981
|
+
if (!iframe?.contentWindow) return;
|
|
7982
|
+
try {
|
|
7983
|
+
const screenshot = await handleChatScreenshot(widgetId, iframe, true);
|
|
7984
|
+
if (!screenshot || codegenCaptureBlocked() || generatingWidgetIds[widgetId]) return;
|
|
7985
|
+
await persistWidgetListThumbnail(widgetId, screenshot);
|
|
7986
|
+
console.log('[Chat→Screenshot] Idle thumbnail persisted', widgetId.substring(0, 8));
|
|
7987
|
+
} catch (error) {
|
|
7988
|
+
console.warn('[Chat→Screenshot] Idle thumbnail persist failed', widgetId, error);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7836
7991
|
function scheduleCanvasShapePreviewSync(widgetId: string, delayMs = 1800): void {
|
|
7837
|
-
if (!widgetId ||
|
|
7992
|
+
if (!widgetId || codegenCaptureBlocked()) return;
|
|
7838
7993
|
const existing = canvasShapePreviewSyncTimers.get(widgetId);
|
|
7839
7994
|
if (existing) clearTimeout(existing);
|
|
7840
7995
|
const timer = setTimeout(() => {
|
|
7841
7996
|
canvasShapePreviewSyncTimers.delete(widgetId);
|
|
7842
|
-
if (
|
|
7997
|
+
if (codegenCaptureBlocked() || generatingWidgetIds[widgetId]) {
|
|
7843
7998
|
// console.log('[CreatorApp] Skip shape preview sync — codegen in progress:', widgetId.substring(0, 8));
|
|
7844
7999
|
return;
|
|
7845
8000
|
}
|
|
@@ -7870,8 +8025,8 @@
|
|
|
7870
8025
|
|
|
7871
8026
|
// Never run quiet background screenshot captures while ANY code generation is running —
|
|
7872
8027
|
// html2canvas execution inside any preview iframe blocks the single shared main thread.
|
|
7873
|
-
if (
|
|
7874
|
-
console.log('[handleRefreshScreenshot] Skip
|
|
8028
|
+
if (codegenCaptureBlocked()) {
|
|
8029
|
+
console.log('[handleRefreshScreenshot] Skip capture — codegen in progress', widgetId?.substring(0, 8));
|
|
7875
8030
|
return;
|
|
7876
8031
|
}
|
|
7877
8032
|
|
|
@@ -7972,6 +8127,12 @@
|
|
|
7972
8127
|
});
|
|
7973
8128
|
}
|
|
7974
8129
|
window.addEventListener('message', handler);
|
|
8130
|
+
if (codegenCaptureBlocked()) {
|
|
8131
|
+
clearTimeout(timeout);
|
|
8132
|
+
window.removeEventListener('message', handler);
|
|
8133
|
+
resolve({ data: null, error: 'generation in progress' });
|
|
8134
|
+
return;
|
|
8135
|
+
}
|
|
7975
8136
|
iframe.contentWindow!.postMessage({ type: 'widgetic:request-screenshot' }, '*');
|
|
7976
8137
|
});
|
|
7977
8138
|
|
|
@@ -8092,6 +8253,8 @@ Requirements:
|
|
|
8092
8253
|
promptImagesOverride?: string[];
|
|
8093
8254
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8094
8255
|
skipIntentGate?: boolean;
|
|
8256
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8257
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8095
8258
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
8096
8259
|
persistFileAttachments?: Array<{
|
|
8097
8260
|
url?: string;
|
|
@@ -8182,6 +8345,7 @@ Requirements:
|
|
|
8182
8345
|
promptImagesOverride: options.promptImagesOverride,
|
|
8183
8346
|
persistFileAttachments: options.persistFileAttachments,
|
|
8184
8347
|
skipIntentGate: options.skipIntentGate,
|
|
8348
|
+
chipIntent: options.chipIntent,
|
|
8185
8349
|
});
|
|
8186
8350
|
}
|
|
8187
8351
|
|
|
@@ -8205,6 +8369,8 @@ Requirements:
|
|
|
8205
8369
|
promptImagesOverride?: string[];
|
|
8206
8370
|
/** Skip the server's intent gate — the clarification card's explicit "generate anyway" choice. */
|
|
8207
8371
|
skipIntentGate?: boolean;
|
|
8372
|
+
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8373
|
+
chipIntent?: ClarificationSuggestion['intent'];
|
|
8208
8374
|
persistFileAttachments?: Array<{
|
|
8209
8375
|
url?: string;
|
|
8210
8376
|
file?: File;
|
|
@@ -8220,7 +8386,6 @@ Requirements:
|
|
|
8220
8386
|
|
|
8221
8387
|
const panelChat = getChatMethodsForWidget(generationWidgetId);
|
|
8222
8388
|
const panelSnap = getPanelState(generationWidgetId);
|
|
8223
|
-
const hadCodeBeforeGeneration = !!(panelSnap?.hasGeneratedCode || panelSnap?.lastCommitId);
|
|
8224
8389
|
|
|
8225
8390
|
// Per-widget abort: only abort if the SAME widget already has a running generation
|
|
8226
8391
|
const existingController = widgetAbortControllers.get(generationWidgetId);
|
|
@@ -8233,6 +8398,22 @@ Requirements:
|
|
|
8233
8398
|
const signal = abortController.signal;
|
|
8234
8399
|
|
|
8235
8400
|
generatingWidgetIds = { ...generatingWidgetIds, [generationWidgetId]: true };
|
|
8401
|
+
noteCodegenActive(true);
|
|
8402
|
+
screenshotGenerationEpoch += 1;
|
|
8403
|
+
document.querySelectorAll('iframe').forEach((frame) => {
|
|
8404
|
+
try {
|
|
8405
|
+
frame.contentWindow?.postMessage({ type: 'widgetic:abort-screenshot' }, '*');
|
|
8406
|
+
} catch (err) {
|
|
8407
|
+
console.warn('[WidgetCreator] Could not abort in-flight screenshot', err);
|
|
8408
|
+
}
|
|
8409
|
+
});
|
|
8410
|
+
cancelQuietScreenshots();
|
|
8411
|
+
cancelIdleThumbnailPersist();
|
|
8412
|
+
widgetShapeLivePreviewsRef?.cancelReloads?.();
|
|
8413
|
+
if (restoredViewingVersionByWidgetId[generationWidgetId] != null) {
|
|
8414
|
+
const { [generationWidgetId]: _droppedRestore, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
8415
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
8416
|
+
}
|
|
8236
8417
|
lastHarnessChatLine = '';
|
|
8237
8418
|
|
|
8238
8419
|
// Show user prompt in chat when triggered outside Chat (confirm dialog / Generate Now).
|
|
@@ -8446,6 +8627,7 @@ Requirements:
|
|
|
8446
8627
|
runtimeLogs: runtimeLogsForRequest,
|
|
8447
8628
|
compileFixOnly: options.compileFixOnly === true,
|
|
8448
8629
|
skipIntentGate: options.skipIntentGate === true,
|
|
8630
|
+
chipIntent: options.chipIntent,
|
|
8449
8631
|
} as GenerateCodeRequest
|
|
8450
8632
|
});
|
|
8451
8633
|
// console.log('[WidgetCreator] Code generation request sent for widget', generationWidgetId.substring(0, 8), 'with model:', coderModelForRequest, 'planner:', plannerModelIdForRequest || 'none', 'conversationId:', activeConversationId || 'none', 'images:', imagesToSend?.length || 0, 'attachmentIds:', attachmentIdsToSend?.length || 0, 'runtimeLogs:', runtimeLogsForRequest?.length ?? 0);
|
|
@@ -8460,7 +8642,7 @@ Requirements:
|
|
|
8460
8642
|
if (gatedData?.gated === true && typeof gatedData.action === 'string' && gatedData.action !== 'generate') {
|
|
8461
8643
|
const gatedAction = gatedData.action as 'answer' | 'redirect' | 'clarify';
|
|
8462
8644
|
const gatedReply: string = gatedData.reply ?? '';
|
|
8463
|
-
const suggestions:
|
|
8645
|
+
const suggestions: ClarificationSuggestion[] = Array.isArray(gatedData.suggestions) ? gatedData.suggestions : [];
|
|
8464
8646
|
|
|
8465
8647
|
clearActiveOperationId();
|
|
8466
8648
|
patchCodegenUiState(generationWidgetId, {
|
|
@@ -8672,8 +8854,10 @@ Requirements:
|
|
|
8672
8854
|
shapeHistoryResolvedByWidgetId = restResolved;
|
|
8673
8855
|
setTimeout(() => {
|
|
8674
8856
|
void loadPublishHistoryForPanel(generationWidgetId, { silent: true }).then(() => {
|
|
8675
|
-
|
|
8676
|
-
if (
|
|
8857
|
+
const newestDraft = getNewestDraftVersionId(generationWidgetId);
|
|
8858
|
+
if (newestDraft != null) {
|
|
8859
|
+
selectVersionInDropdowns(generationWidgetId, `v:${newestDraft}`);
|
|
8860
|
+
}
|
|
8677
8861
|
});
|
|
8678
8862
|
}, 1200);
|
|
8679
8863
|
}
|
|
@@ -8845,11 +9029,9 @@ Requirements:
|
|
|
8845
9029
|
[generationWidgetId]: 'preview',
|
|
8846
9030
|
};
|
|
8847
9031
|
syncWidgetShapeLivePreviews();
|
|
8848
|
-
//
|
|
8849
|
-
//
|
|
8850
|
-
// on the canvas shape overlay or it keeps showing the previous version.
|
|
9032
|
+
// WS preview-ready was deferred during codegen so the Worker
|
|
9033
|
+
// iframe does not boot on the shared main thread at 80%.
|
|
8851
9034
|
widgetShapeLivePreviewsRef?.requestReload?.(generationWidgetId);
|
|
8852
|
-
panelDisplayRevision += 1;
|
|
8853
9035
|
if (!completedPreviewBuildErr) {
|
|
8854
9036
|
await applyDesignSchemaDefaultsToPreview(generationWidgetId);
|
|
8855
9037
|
pendingCreatePreviewDefaultsWidgetId = null;
|
|
@@ -8863,15 +9045,10 @@ Requirements:
|
|
|
8863
9045
|
await panelChat?.reloadConversationMessages?.();
|
|
8864
9046
|
panelChat?.markWidgetHeadAtLatestCommit?.();
|
|
8865
9047
|
}, 2500);
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
void (async () => {
|
|
8871
|
-
await handleRefreshScreenshot(generationWidgetId, { quiet: true });
|
|
8872
|
-
})();
|
|
8873
|
-
}
|
|
8874
|
-
}, 3000);
|
|
9048
|
+
scheduleIdleThumbnailPersist(generationWidgetId);
|
|
9049
|
+
// Do not html2canvas here. Capture during/after generation freezes
|
|
9050
|
+
// the tab (iframe shares the main thread). Thumb for the next prompt
|
|
9051
|
+
// is persisted later, only while the tab is idle.
|
|
8875
9052
|
// Do NOT auto-publish — user previews first, then clicks Publish / Set live.
|
|
8876
9053
|
} else {
|
|
8877
9054
|
panelChat?.updateLatestCodegenStatusMessage?.(
|
|
@@ -8949,6 +9126,7 @@ Requirements:
|
|
|
8949
9126
|
showToast('error', 'Code generation error', { description: message, duration: 6000 });
|
|
8950
9127
|
pushCodegenFailureToChat(generationWidgetId, message);
|
|
8951
9128
|
} finally {
|
|
9129
|
+
noteCodegenActive(false);
|
|
8952
9130
|
const { [generationWidgetId]: _done, ...restFinally } = generatingWidgetIds;
|
|
8953
9131
|
generatingWidgetIds = restFinally;
|
|
8954
9132
|
widgetAbortControllers.delete(generationWidgetId);
|
|
@@ -9107,7 +9285,7 @@ Requirements:
|
|
|
9107
9285
|
// rather than letting them silently become version 0.
|
|
9108
9286
|
const rawTargetVersion = publishOpts.targetVersion;
|
|
9109
9287
|
const targetVersion =
|
|
9110
|
-
rawTargetVersion == null || rawTargetVersion === ''
|
|
9288
|
+
rawTargetVersion == null || (typeof rawTargetVersion === 'string' && rawTargetVersion === '')
|
|
9111
9289
|
? null
|
|
9112
9290
|
: Number.isFinite(Number(rawTargetVersion))
|
|
9113
9291
|
? Number(rawTargetVersion)
|
|
@@ -10296,8 +10474,8 @@ Requirements:
|
|
|
10296
10474
|
record?.artifact_url ||
|
|
10297
10475
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10298
10476
|
if (isPublished && artifact) {
|
|
10299
|
-
const
|
|
10300
|
-
if (url) return url;
|
|
10477
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10478
|
+
if (isolated?.url) return isolated.url;
|
|
10301
10479
|
}
|
|
10302
10480
|
const previewState = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10303
10481
|
if (previewState?.url) return previewState.url;
|
|
@@ -10327,6 +10505,10 @@ Requirements:
|
|
|
10327
10505
|
* Publish history is loaded without requiring chat to be open.
|
|
10328
10506
|
*/
|
|
10329
10507
|
let shapePreviewSourceByWidgetId: Record<string, string> = {};
|
|
10508
|
+
/** Dropdown label only. Changing it must not reload the shape iframe. */
|
|
10509
|
+
let dropdownVersionByWidgetId: Record<string, string> = {};
|
|
10510
|
+
/** After Restore Checkpoint, dropdowns stay on the restored version number while preview is Worker HEAD. */
|
|
10511
|
+
let restoredViewingVersionByWidgetId: Record<string, number> = {};
|
|
10330
10512
|
// Historical previews are never interchangeable with the shared HEAD preview.
|
|
10331
10513
|
let shapeVersionPreviewByWidgetId: Record<string, {
|
|
10332
10514
|
versionId: string; requestId: number; loading: boolean; error: string | null; url: string | null;
|
|
@@ -10440,12 +10622,21 @@ Requirements:
|
|
|
10440
10622
|
(version === ps.lastPublishedVersion ? ps.selectedWidgetJsPath : null);
|
|
10441
10623
|
|
|
10442
10624
|
if (isPublished && artifact) {
|
|
10443
|
-
const
|
|
10444
|
-
if (url) return url;
|
|
10625
|
+
const isolated = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10626
|
+
if (isolated?.url && isolated.versionId === `v:${version}`) return isolated.url;
|
|
10627
|
+
// Do not return Live CDN here — Switch Version must wait for the
|
|
10628
|
+
// commit-specific build, otherwise the iframe stays on the last publish.
|
|
10445
10629
|
}
|
|
10446
|
-
// The newest draft
|
|
10630
|
+
// The newest draft is repo HEAD only while HEAD still points at its commit.
|
|
10631
|
+
// A Restore Checkpoint writes a rollback commit on top, so the shared
|
|
10632
|
+
// `/preview/{widgetId}` artifact then serves the restored code under the
|
|
10633
|
+
// newest version's number — ask for a commit-specific build instead.
|
|
10447
10634
|
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10448
|
-
|
|
10635
|
+
const draftCommitSha = normalizeCommitSha(record?.commitSha ?? record?.commit_sha ?? null);
|
|
10636
|
+
const headCommitSha = normalizeCommitSha(ps.lastCommitId ?? loadCommitIdForWidget(widgetId));
|
|
10637
|
+
const draftIsRepoHead =
|
|
10638
|
+
!draftCommitSha || !headCommitSha || commitsMatch(draftCommitSha, headCommitSha);
|
|
10639
|
+
if (!isPublished && newestDraft != null && version === newestDraft && draftIsRepoHead && workerUrl) {
|
|
10449
10640
|
return workerUrl;
|
|
10450
10641
|
}
|
|
10451
10642
|
return resolveWidgetShapeVersionPreviewState(widgetId)?.url ?? null;
|
|
@@ -10464,6 +10655,8 @@ Requirements:
|
|
|
10464
10655
|
}
|
|
10465
10656
|
|
|
10466
10657
|
function resolveWidgetShapeVersionLabel(widgetId: string): string | null {
|
|
10658
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10659
|
+
if (Number.isFinite(restored)) return `v${restored}`;
|
|
10467
10660
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10468
10661
|
const ps = getPanelDisplayState(widgetId);
|
|
10469
10662
|
const liveVersion =
|
|
@@ -10526,6 +10719,7 @@ Requirements:
|
|
|
10526
10719
|
dateLabel?: string;
|
|
10527
10720
|
isLive?: boolean;
|
|
10528
10721
|
isDraft?: boolean;
|
|
10722
|
+
commitSha?: string;
|
|
10529
10723
|
}> = [];
|
|
10530
10724
|
for (const record of history) {
|
|
10531
10725
|
const version = Number(record.version);
|
|
@@ -10564,6 +10758,10 @@ Requirements:
|
|
|
10564
10758
|
}
|
|
10565
10759
|
|
|
10566
10760
|
function resolveWidgetShapeVersionSelectedId(widgetId: string): string | null {
|
|
10761
|
+
const menuVersion = dropdownVersionByWidgetId[widgetId];
|
|
10762
|
+
if (menuVersion) return menuVersion;
|
|
10763
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10764
|
+
if (Number.isFinite(restored)) return `v:${restored}`;
|
|
10567
10765
|
const source = shapePreviewSourceByWidgetId[widgetId];
|
|
10568
10766
|
if (source?.startsWith('v:')) return source;
|
|
10569
10767
|
if (source === 'preview') {
|
|
@@ -10579,8 +10777,21 @@ Requirements:
|
|
|
10579
10777
|
return liveVersion != null ? `v:${liveVersion}` : null;
|
|
10580
10778
|
}
|
|
10581
10779
|
|
|
10780
|
+
function selectVersionInDropdowns(widgetId: string, versionId: string): void {
|
|
10781
|
+
dropdownVersionByWidgetId = { ...dropdownVersionByWidgetId, [widgetId]: versionId };
|
|
10782
|
+
widgetShapeLivePreviewsRef?.setSelectedVersion?.(widgetId, versionId);
|
|
10783
|
+
panelDisplayRevision += 1;
|
|
10784
|
+
}
|
|
10785
|
+
|
|
10582
10786
|
function handleWidgetShapeVersionChange(widgetId: string, versionId: string, buildPreview = true): void {
|
|
10583
10787
|
if (!versionId) return;
|
|
10788
|
+
// Both dropdowns show the pick. Only the dropdown the user clicked reloads the preview.
|
|
10789
|
+
selectVersionInDropdowns(widgetId, versionId);
|
|
10790
|
+
if (!buildPreview) return;
|
|
10791
|
+
if (restoredViewingVersionByWidgetId[widgetId] != null) {
|
|
10792
|
+
const { [widgetId]: _dropped, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
10793
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
10794
|
+
}
|
|
10584
10795
|
// Re-selecting an already successful/in-flight preview must not reset its state.
|
|
10585
10796
|
const currentPreview = resolveWidgetShapeVersionPreviewState(widgetId);
|
|
10586
10797
|
if (shapePreviewSourceByWidgetId[widgetId] === versionId
|
|
@@ -10599,17 +10810,19 @@ Requirements:
|
|
|
10599
10810
|
};
|
|
10600
10811
|
syncWidgetShapeLivePreviews();
|
|
10601
10812
|
panelDisplayRevision += 1;
|
|
10602
|
-
//
|
|
10603
|
-
|
|
10604
|
-
//
|
|
10813
|
+
// Always compile the selected commit into an isolated Worker preview.
|
|
10814
|
+
// Published CDN artifacts are Live only — using them on Switch made
|
|
10815
|
+
// draft/restore look like the last publish even when git had moved.
|
|
10605
10816
|
if (versionId.startsWith('v:')) {
|
|
10606
10817
|
const version = Number(versionId.slice(2));
|
|
10607
|
-
const
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10818
|
+
const record = getShapePublishHistory(widgetId).find((row) => Number(row.version) === version);
|
|
10819
|
+
const isPublished = !!(record?.publishedAt ?? record?.published_at);
|
|
10820
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10821
|
+
const workerUrl = resolveWidgetShapeLivePreviewUrl(widgetId);
|
|
10822
|
+
const viewingHeadDraft = !isPublished && newestDraft != null && version === newestDraft && workerUrl && !restoredViewingVersionByWidgetId[widgetId];
|
|
10823
|
+
if (viewingHeadDraft) {
|
|
10611
10824
|
const detailsRef = getWidgetDetailsRef(widgetId);
|
|
10612
|
-
detailsRef?.showPublishedArtifact?.(
|
|
10825
|
+
detailsRef?.showPublishedArtifact?.(workerUrl);
|
|
10613
10826
|
} else {
|
|
10614
10827
|
void ensureWidgetShapeVersionPreview(widgetId, version);
|
|
10615
10828
|
}
|
|
@@ -10688,6 +10901,13 @@ Requirements:
|
|
|
10688
10901
|
/** Commit SHA for the version currently selected in the version menu (drives Head-at-commit badge). */
|
|
10689
10902
|
function resolveViewingCommitShaForPanel(widgetId: string): string | null {
|
|
10690
10903
|
const ps = getPanelDisplayState(widgetId);
|
|
10904
|
+
const restored = restoredViewingVersionByWidgetId[widgetId];
|
|
10905
|
+
if (Number.isFinite(restored)) {
|
|
10906
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10907
|
+
const record = history.find((r) => Number(r.version) === Number(restored));
|
|
10908
|
+
const fromRestored = normalizeCommitSha(record?.commitSha ?? record?.commit_sha);
|
|
10909
|
+
if (fromRestored) return fromRestored;
|
|
10910
|
+
}
|
|
10691
10911
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
10692
10912
|
if (source === 'preview') {
|
|
10693
10913
|
return normalizeCommitSha(ps.lastCommitId);
|
|
@@ -10738,18 +10958,28 @@ Requirements:
|
|
|
10738
10958
|
* debug mode and this label is what the turn shows.
|
|
10739
10959
|
*/
|
|
10740
10960
|
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10961
|
+
let versionPromptLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10962
|
+
let viewingVersionByWidgetId: Record<string, number | null> = {};
|
|
10741
10963
|
$: {
|
|
10742
10964
|
void shapePreviewSourceByWidgetId;
|
|
10743
10965
|
void shapeVersionPreviewByWidgetId;
|
|
10966
|
+
void restoredViewingVersionByWidgetId;
|
|
10967
|
+
void dropdownVersionByWidgetId;
|
|
10744
10968
|
void panelDisplayRevision;
|
|
10745
10969
|
const nextHeads: Record<string, string | null> = {};
|
|
10746
10970
|
const nextLabels: Record<string, Record<string, string>> = {};
|
|
10971
|
+
const nextPrompts: Record<string, Record<string, string>> = {};
|
|
10972
|
+
const nextViewing: Record<string, number | null> = {};
|
|
10747
10973
|
for (const widgetId of openWidgetPanelIds) {
|
|
10748
10974
|
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
10749
10975
|
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
10976
|
+
nextPrompts[widgetId] = buildVersionPromptLabelsForPanel(widgetId);
|
|
10977
|
+
nextViewing[widgetId] = viewingVersionNumberForPanel(widgetId);
|
|
10750
10978
|
}
|
|
10751
10979
|
headCommitShaByWidgetId = nextHeads;
|
|
10752
10980
|
versionLabelsByWidgetId = nextLabels;
|
|
10981
|
+
versionPromptLabelsByWidgetId = nextPrompts;
|
|
10982
|
+
viewingVersionByWidgetId = nextViewing;
|
|
10753
10983
|
}
|
|
10754
10984
|
|
|
10755
10985
|
/** Commit shas are keyed on 7 chars — that is what the chat summaries carry. */
|
|
@@ -10758,24 +10988,55 @@ Requirements:
|
|
|
10758
10988
|
return normalized ? normalized.slice(0, 7) : null;
|
|
10759
10989
|
}
|
|
10760
10990
|
|
|
10991
|
+
function assignVersionLabelKeys(labels: Record<string, string>, sha: string | null | undefined, label: string): void {
|
|
10992
|
+
const normalized = normalizeCommitSha(sha);
|
|
10993
|
+
if (!normalized) return;
|
|
10994
|
+
labels[normalized] = label;
|
|
10995
|
+
labels[normalized.slice(0, 7)] = label;
|
|
10996
|
+
labels[normalized.slice(0, 8)] = label;
|
|
10997
|
+
}
|
|
10998
|
+
|
|
10761
10999
|
function buildVersionLabelsForPanel(widgetId: string): Record<string, string> {
|
|
10762
11000
|
const labels: Record<string, string> = {};
|
|
10763
11001
|
const ps = getPanelDisplayState(widgetId);
|
|
10764
11002
|
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
10765
11003
|
for (const record of history || []) {
|
|
10766
|
-
|
|
10767
|
-
|
|
11004
|
+
if (record?.version == null) continue;
|
|
11005
|
+
assignVersionLabelKeys(labels, record?.commitSha ?? record?.commit_sha, `Version ${record.version}`);
|
|
10768
11006
|
}
|
|
10769
|
-
// The version the user is previewing right now may have been fetched rather
|
|
10770
|
-
// than read from history, and it is the one whose label matters most.
|
|
10771
11007
|
const previewState = shapeVersionPreviewByWidgetId[widgetId];
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
11008
|
+
if (previewState?.versionId?.startsWith('v:')) {
|
|
11009
|
+
assignVersionLabelKeys(labels, previewState?.commitSha, `Version ${previewState.versionId.slice(2)}`);
|
|
11010
|
+
}
|
|
11011
|
+
return labels;
|
|
11012
|
+
}
|
|
11013
|
+
|
|
11014
|
+
function versionPromptKey(prompt: string | null | undefined): string | null {
|
|
11015
|
+
const text = String(prompt || '').replace(/\s+/g, ' ').trim();
|
|
11016
|
+
return text || null;
|
|
11017
|
+
}
|
|
11018
|
+
|
|
11019
|
+
/** User prompt → "Version N", for turns whose commit line was never saved. */
|
|
11020
|
+
function buildVersionPromptLabelsForPanel(widgetId: string): Record<string, string> {
|
|
11021
|
+
const labels: Record<string, string> = {};
|
|
11022
|
+
const ps = getPanelDisplayState(widgetId);
|
|
11023
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
11024
|
+
for (const record of history || []) {
|
|
11025
|
+
if (record?.version == null) continue;
|
|
11026
|
+
const key = versionPromptKey(record?.prompt);
|
|
11027
|
+
if (!key) continue;
|
|
11028
|
+
labels[key] = `Version ${record.version}`;
|
|
10775
11029
|
}
|
|
10776
11030
|
return labels;
|
|
10777
11031
|
}
|
|
10778
11032
|
|
|
11033
|
+
function viewingVersionNumberForPanel(widgetId: string): number | null {
|
|
11034
|
+
const selected = resolveWidgetShapeVersionSelectedId(widgetId);
|
|
11035
|
+
if (!selected?.startsWith('v:')) return null;
|
|
11036
|
+
const version = parseInt(selected.slice(2), 10);
|
|
11037
|
+
return Number.isFinite(version) ? version : null;
|
|
11038
|
+
}
|
|
11039
|
+
|
|
10779
11040
|
function syncWidgetShapeLivePreviews(): void {
|
|
10780
11041
|
measureSync('sync-widget-shape-previews', () => {
|
|
10781
11042
|
try {
|
|
@@ -11010,10 +11271,13 @@ Requirements:
|
|
|
11010
11271
|
if (embedded && deepLinkId && widgetId === deepLinkId) {
|
|
11011
11272
|
embeddedDeepLinkPanelDismissed = false;
|
|
11012
11273
|
}
|
|
11013
|
-
//
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11274
|
+
// At most two chats. Opening a third closes the oldest one.
|
|
11275
|
+
const maxOpenWidgetChats = 2;
|
|
11276
|
+
if (!openWidgetPanelIds.includes(widgetId)) {
|
|
11277
|
+
while (openWidgetPanelIds.length >= maxOpenWidgetChats) {
|
|
11278
|
+
const oldestId = openWidgetPanelIds[0];
|
|
11279
|
+
if (!oldestId) break;
|
|
11280
|
+
closeWidgetDetailsPanel(oldestId, { fromShapeSwitch: true });
|
|
11017
11281
|
}
|
|
11018
11282
|
}
|
|
11019
11283
|
const isNewPanel = !openWidgetPanelIds.includes(widgetId);
|
|
@@ -12021,7 +12285,7 @@ Requirements:
|
|
|
12021
12285
|
* from whatever HEAD happens to contain.
|
|
12022
12286
|
*/
|
|
12023
12287
|
function canPublishDraftVersionForPanel(widgetId: string, version: number | null | undefined): boolean {
|
|
12024
|
-
if (version == null || version === '' || !Number.isFinite(Number(version))) return false;
|
|
12288
|
+
if (version == null || (typeof version === 'string' && version === '') || !Number.isFinite(Number(version))) return false;
|
|
12025
12289
|
if (isPublishing && publishingWidgetId === widgetId) return false;
|
|
12026
12290
|
if (isPublishing && publishingWidgetId !== null && publishingWidgetId !== widgetId) return false;
|
|
12027
12291
|
if (generatingWidgetIds[widgetId]) return false;
|
|
@@ -12150,7 +12414,7 @@ $: if ($widgetPublishStatus.timestamp && $widgetPublishStatus.widgetId) {
|
|
|
12150
12414
|
}
|
|
12151
12415
|
if (publishWidgetId) {
|
|
12152
12416
|
// console.log('[Publish] Refreshing canvas widget screenshot after successful publish');
|
|
12153
|
-
|
|
12417
|
+
scheduleQuietScreenshot(publishWidgetId, 2000);
|
|
12154
12418
|
}
|
|
12155
12419
|
if (embedded && onPublishComplete && publishWidgetId) {
|
|
12156
12420
|
onPublishComplete({
|
|
@@ -12198,7 +12462,7 @@ $: if ($widgetPublishStatus.widgetId === selectedWidgetId && $widgetPublishStatu
|
|
|
12198
12462
|
// console.log('[CrossTab/WS] Publish building status from WebSocket — disabling Publish button');
|
|
12199
12463
|
}
|
|
12200
12464
|
|
|
12201
|
-
// Agent harness repair status (DW Phase 2) — real-time via
|
|
12465
|
+
// Agent harness repair status (DW Phase 2) — real-time via codegen:output WebSocket.
|
|
12202
12466
|
$: if ($agentHarnessOutput?.timestamp) {
|
|
12203
12467
|
const raw = ($agentHarnessOutput.output || '').trim();
|
|
12204
12468
|
const line = raw.replace(/^🔧\s*/, '').trim();
|
|
@@ -12214,15 +12478,27 @@ $: if ($agentHarnessOutput?.timestamp) {
|
|
|
12214
12478
|
}
|
|
12215
12479
|
}
|
|
12216
12480
|
|
|
12217
|
-
// Worker preview ready via WebSocket event — reload canvas shape preview immediately
|
|
12481
|
+
// Worker preview ready via WebSocket event — reload canvas shape preview immediately.
|
|
12482
|
+
// The store keeps the last event forever, and this block writes state it also reads,
|
|
12483
|
+
// so without the event key it re-ran on every unrelated update: each re-run snapped the
|
|
12484
|
+
// version selector back to HEAD and cancelled an in-flight version preview.
|
|
12485
|
+
let lastWsWorkerPreviewEventKey: string | null = null;
|
|
12218
12486
|
$: if ($dynamicWorkerPreviewStatus.widgetId && $dynamicWorkerPreviewStatus.previewUrl) {
|
|
12219
12487
|
const wsReadyWidgetId = $dynamicWorkerPreviewStatus.widgetId;
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12488
|
+
const wsEventKey = `${wsReadyWidgetId}:${$dynamicWorkerPreviewStatus.previewUrl}:${$dynamicWorkerPreviewStatus.timestamp ?? ''}`;
|
|
12489
|
+
if (wsEventKey !== lastWsWorkerPreviewEventKey) {
|
|
12490
|
+
lastWsWorkerPreviewEventKey = wsEventKey;
|
|
12491
|
+
shapePreviewSourceByWidgetId = {
|
|
12492
|
+
...shapePreviewSourceByWidgetId,
|
|
12493
|
+
[wsReadyWidgetId]: 'preview',
|
|
12494
|
+
};
|
|
12495
|
+
if (codegenCaptureBlocked()) {
|
|
12496
|
+
console.log('[CreatorApp] Defer canvas Worker preview reload until codegen finishes', wsReadyWidgetId.substring(0, 8));
|
|
12497
|
+
} else {
|
|
12498
|
+
syncWidgetShapeLivePreviews();
|
|
12499
|
+
widgetShapeLivePreviewsRef?.requestReload?.(wsReadyWidgetId);
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12226
12502
|
}
|
|
12227
12503
|
|
|
12228
12504
|
// ===================== Publish History =====================
|
|
@@ -12898,6 +13174,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
12898
13174
|
* parent-page [FreezeReport] (long tasks attributed to the iframe).
|
|
12899
13175
|
*/
|
|
12900
13176
|
const screenshotCaptureInFlight = new Set<string>();
|
|
13177
|
+
/** Bumped when a generation starts so in-flight html2canvas results are dropped. */
|
|
13178
|
+
let screenshotGenerationEpoch = 0;
|
|
12901
13179
|
|
|
12902
13180
|
/**
|
|
12903
13181
|
* Called when a Widget Image shape is clicked on the canvas.
|
|
@@ -14988,7 +15266,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14988
15266
|
{@const panelInstanceKey = panelInstanceKeyByWidgetId[panelWidgetId] ?? panelWidgetId}
|
|
14989
15267
|
{@const panelWidget = widgetsById.get(panelWidgetId) ?? null}
|
|
14990
15268
|
{@const isPanelFocused = focusedPanelWidgetId === panelWidgetId}
|
|
14991
|
-
{@const ps = (panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
15269
|
+
{@const ps = rev(panelDisplayRevision, getPanelDisplayState(panelWidgetId))}
|
|
14992
15270
|
<!--
|
|
14993
15271
|
Same published-version resolution the WidgetDetails instance below gets.
|
|
14994
15272
|
`ps.lastPublishedVersion` alone is only hydrated from the focused panel /
|
|
@@ -14999,9 +15277,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
14999
15277
|
{@const panelPublishedVersion = isDraftConvertId(panelWidgetId)
|
|
15000
15278
|
? null
|
|
15001
15279
|
: (ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null))}
|
|
15002
|
-
{@const panelHasUnpublishedChanges = (panelDisplayRevision,
|
|
15003
|
-
{@const panelHasCompileErrorState = (panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
15004
|
-
{@const panelHasInfraPreviewError = (panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
15280
|
+
{@const panelHasUnpublishedChanges = rev(panelDisplayRevision, hasUnpublishedChangesForPanel(panelWidgetId), widgets.length)}
|
|
15281
|
+
{@const panelHasCompileErrorState = rev(panelDisplayRevision, panelHasCompileError(panelWidgetId))}
|
|
15282
|
+
{@const panelHasInfraPreviewError = rev(panelDisplayRevision, panelHasInfrastructurePreviewError(panelWidgetId))}
|
|
15005
15283
|
<svelte:component this={WidgetDetails}
|
|
15006
15284
|
bind:this={widgetDetailsRefs[panelInstanceKey]}
|
|
15007
15285
|
userSession={$userSession}
|
|
@@ -15330,17 +15608,19 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15330
15608
|
{@const defaultVersionSource = panelPublishedVersion !== null
|
|
15331
15609
|
? `v:${panelPublishedVersion}`
|
|
15332
15610
|
: (newestDraft ? `v:${newestDraft.version}` : null)}
|
|
15333
|
-
{@const
|
|
15334
|
-
{@const
|
|
15611
|
+
{@const restoredVersion = restoredViewingVersionByWidgetId[panelWidgetId]}
|
|
15612
|
+
{@const menuVersion = dropdownVersionByWidgetId[panelWidgetId]}
|
|
15613
|
+
{@const shapeSource = menuVersion || shapePreviewSourceByWidgetId[panelWidgetId] || defaultVersionSource}
|
|
15614
|
+
{@const viewingVersion = Number.isFinite(restoredVersion)
|
|
15615
|
+
? restoredVersion
|
|
15616
|
+
: shapeSource && shapeSource.startsWith('v:')
|
|
15335
15617
|
? parseInt(shapeSource.slice(2), 10)
|
|
15336
15618
|
: shapeSource === 'preview'
|
|
15337
|
-
|
|
15338
|
-
// version — falling back to panelPublishedVersion produced `vnull`
|
|
15339
|
-
// right after generation AND showed the stale Live version when a
|
|
15340
|
-
// draft existed. The newest draft IS this HEAD's version.
|
|
15341
|
-
? (newestDraft ? newestDraft.version : panelPublishedVersion)
|
|
15619
|
+
? (getNewestDraftVersionId(panelWidgetId) ?? panelPublishedVersion)
|
|
15342
15620
|
: panelPublishedVersion}
|
|
15343
|
-
{@const viewingIsLive = Number(
|
|
15621
|
+
{@const viewingIsLive = !Number.isFinite(restoredVersion)
|
|
15622
|
+
&& shapeSource !== 'preview'
|
|
15623
|
+
&& Number(viewingVersion) === Number(panelPublishedVersion)}
|
|
15344
15624
|
<!--
|
|
15345
15625
|
A version is a Draft until it is published. The history is one
|
|
15346
15626
|
sequence (`v1, v2, v3…`), so "draft" is read from the record's
|
|
@@ -15541,9 +15821,16 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15541
15821
|
<Button
|
|
15542
15822
|
onclick={() => {
|
|
15543
15823
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15824
|
+
patchPanelSnapshot(panelWidgetId, {
|
|
15825
|
+
lastGenerationFailed: false,
|
|
15826
|
+
lastGenerationError: null,
|
|
15827
|
+
generateCodeStatus: 'Ready',
|
|
15828
|
+
});
|
|
15829
|
+
if (isPanelFocused || focusedPanelWidgetId === panelWidgetId) {
|
|
15830
|
+
lastGenerationFailed = false;
|
|
15831
|
+
lastGenerationError = null;
|
|
15832
|
+
generateCodeStatus = 'Ready';
|
|
15833
|
+
}
|
|
15547
15834
|
}}
|
|
15548
15835
|
variant="outline"
|
|
15549
15836
|
size="sm"
|
|
@@ -15558,8 +15845,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15558
15845
|
</svelte:fragment>
|
|
15559
15846
|
|
|
15560
15847
|
<svelte:fragment slot="code-generation">
|
|
15848
|
+
{@const chatUserHeight = rev(panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15561
15849
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
15562
|
-
{@const chatUserHeight = (panelDisplayRevision, getChatContainerHeightForPanel(panelWidgetId))}
|
|
15563
15850
|
<div
|
|
15564
15851
|
class="generation-scroll-ct flex flex-col h-full min-h-0 overflow-y-auto border-t border-gray-200"
|
|
15565
15852
|
style="padding: 0;"
|
|
@@ -15605,6 +15892,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15605
15892
|
}}
|
|
15606
15893
|
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15607
15894
|
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15895
|
+
versionLabelByPrompt={versionPromptLabelsByWidgetId[panelWidgetId] || {}}
|
|
15896
|
+
viewingVersion={viewingVersionByWidgetId[panelWidgetId] ?? null}
|
|
15608
15897
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15609
15898
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
15610
15899
|
onUploadChatFile={handleChatUploadFile}
|
|
@@ -15614,8 +15903,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15614
15903
|
onLoadConversations={handleLoadConversations}
|
|
15615
15904
|
onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
|
|
15616
15905
|
onCreateConversation={handleCreateConversation}
|
|
15617
|
-
onSaveMessage={(conversationId, content, messageType, attachments) =>
|
|
15618
|
-
handleSaveMessage(conversationId, content, messageType, attachments, panelWidgetId)
|
|
15906
|
+
onSaveMessage={(conversationId, content, messageType, attachments, suggestions) =>
|
|
15907
|
+
handleSaveMessage(conversationId, content, messageType, attachments, suggestions, panelWidgetId)
|
|
15619
15908
|
}
|
|
15620
15909
|
onRestoreCheckpointBackend={(conversationId, checkpointMessageId) =>
|
|
15621
15910
|
handleRestoreCheckpoint(conversationId, checkpointMessageId, panelWidgetId)
|
|
@@ -15906,11 +16195,13 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15906
16195
|
|
|
15907
16196
|
<!-- Embed Dimensions -->
|
|
15908
16197
|
<div class="embed-dimensions-ct flex flex-col gap-2">
|
|
15909
|
-
<label class="text-xs font-medium text-gray-600">Dimensions</label>
|
|
16198
|
+
<label class="text-xs font-medium text-gray-600" for="embed-width-input">Dimensions</label>
|
|
15910
16199
|
<div class="embed-dimensions-row flex items-center gap-2">
|
|
15911
16200
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15912
16201
|
<span class="text-[10px] text-gray-400 font-medium">W</span>
|
|
15913
16202
|
<input
|
|
16203
|
+
id="embed-width-input"
|
|
16204
|
+
aria-label="Embed width"
|
|
15914
16205
|
type="number"
|
|
15915
16206
|
min="100"
|
|
15916
16207
|
max="4000"
|
|
@@ -15929,6 +16220,8 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15929
16220
|
<div class="embed-dim-input flex items-center gap-1">
|
|
15930
16221
|
<span class="text-[10px] text-gray-400 font-medium">H</span>
|
|
15931
16222
|
<input
|
|
16223
|
+
id="embed-height-input"
|
|
16224
|
+
aria-label="Embed height"
|
|
15932
16225
|
type="number"
|
|
15933
16226
|
min="50"
|
|
15934
16227
|
max="4000"
|
|
@@ -15974,6 +16267,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15974
16267
|
<!-- Embed Code Block -->
|
|
15975
16268
|
<div class="embed-code-ct flex flex-col gap-2">
|
|
15976
16269
|
<div class="embed-code-header flex items-center justify-between">
|
|
16270
|
+
<!-- svelte-ignore a11y_label_has_associated_control -->
|
|
15977
16271
|
<label class="text-xs font-medium text-gray-600">Embed Code ({EMBED_FORMAT_LABELS[effectiveFormat]})</label>
|
|
15978
16272
|
<!-- svelte-ignore a11y_consider_explicit_label -->
|
|
15979
16273
|
<button
|
|
@@ -16095,8 +16389,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16095
16389
|
<p class="text-sm text-gray-600 mb-4">
|
|
16096
16390
|
Permanently delete <strong>{adminDeleteWidget?.name || adminDeleteWidget?.id?.slice(0, 8)}</strong>? This action cannot be undone.
|
|
16097
16391
|
</p>
|
|
16098
|
-
<label class="block text-sm font-medium text-gray-700 mb-1">Admin API Key</label>
|
|
16392
|
+
<label class="block text-sm font-medium text-gray-700 mb-1" for="admin-delete-api-key">Admin API Key</label>
|
|
16099
16393
|
<input
|
|
16394
|
+
id="admin-delete-api-key"
|
|
16100
16395
|
type="password"
|
|
16101
16396
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-red-500 focus:border-red-500 outline-none"
|
|
16102
16397
|
placeholder="Enter admin key..."
|
|
@@ -16355,11 +16650,6 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16355
16650
|
:global([data-dialog-overlay]) {
|
|
16356
16651
|
z-index: 9998 !important;
|
|
16357
16652
|
}
|
|
16358
|
-
.generation-models-header select:focus,
|
|
16359
|
-
.generation-models-header select:focus-visible {
|
|
16360
|
-
outline: none;
|
|
16361
|
-
box-shadow: none;
|
|
16362
|
-
}
|
|
16363
16653
|
.wc-version-history-toggle:focus,
|
|
16364
16654
|
.wc-version-history-toggle:focus-visible,
|
|
16365
16655
|
.wc-live-history-backdrop:focus,
|