@widgetic/creator 0.3.70 → 0.3.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/CreatorApp.svelte
CHANGED
|
@@ -891,6 +891,16 @@
|
|
|
891
891
|
let currentGenerationOperationId: string | null = null;
|
|
892
892
|
let generateCodeStatus: string | null = null;
|
|
893
893
|
let lastGenerationFailed = false; // Track if last generation failed for retry UI
|
|
894
|
+
let publishPlanMessageByWidgetId: Record<string, string> = {};
|
|
895
|
+
|
|
896
|
+
function isPlanLimitMessage(message: string | null | undefined): boolean {
|
|
897
|
+
if (!message) return false;
|
|
898
|
+
return /credit balance|free trial|not available in your plan|not available on the free plan|upgrade your plan|upgrade to a solo/i.test(message);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function openUpgradePlans() {
|
|
902
|
+
window.location.assign('/workspaces/personal/settings/billing');
|
|
903
|
+
}
|
|
894
904
|
let lastGenerationError: string | null = null; // Store the error message for display
|
|
895
905
|
let lastGenerationPrompt: string | null = null; // Last prompt used, for retrying failed generations
|
|
896
906
|
let pendingAutoGenerateWidgetId: string | null = null; // Widget created from canvas that needs auto code generation
|
|
@@ -1194,7 +1204,12 @@
|
|
|
1194
1204
|
// console.log('[Chat] Codegen failure bubble:', content.slice(0, 160));
|
|
1195
1205
|
}
|
|
1196
1206
|
|
|
1197
|
-
/**
|
|
1207
|
+
/** A retry of a prompt that already produced a version opens a new message. A failed retry stays on the same bubble. */
|
|
1208
|
+
function messageTextHasCommit(content: string | null | undefined): boolean {
|
|
1209
|
+
return /(?:Commit:?\s+|Code saved\s*\()[a-f0-9]{7,40}/i.test(String(content || ''));
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/** Retry failed codegen without duplicating the user bubble. A successful prompt forks a new message so each version can show "You are here". */
|
|
1198
1213
|
async function handleChatRetryMessage(content: string, sourceWidgetId?: string) {
|
|
1199
1214
|
const widgetId = sourceWidgetId ?? selectedWidgetId;
|
|
1200
1215
|
if (!widgetId) return;
|
|
@@ -1212,44 +1227,63 @@
|
|
|
1212
1227
|
const draftUploadIds = panelChat?.getAttachmentUploadIds?.() ?? [];
|
|
1213
1228
|
const draftImageUrls = panelChat?.getAttachmentDataUrls?.() ?? [];
|
|
1214
1229
|
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1230
|
+
// A prompt that already has a commit must not reuse that bubble: Version N and
|
|
1231
|
+
// Version N+1 would share one message, and "You are here" could not mark the older one.
|
|
1232
|
+
// The standing sketch is attached on the server for the model. It is not copied onto the chat row.
|
|
1233
|
+
let forkUserMessage = false;
|
|
1217
1234
|
let retryFileAttachments: Array<{ url: string; file_type: string; file_name: string | null; file_size: number | null }> = [];
|
|
1218
1235
|
if (draftUploadIds.length === 0 && draftImageUrls.length === 0) {
|
|
1219
1236
|
try {
|
|
1220
1237
|
const panelState = getPanelState(widgetId);
|
|
1221
1238
|
const convId = panelChat?.getConversationId?.() ?? panelState.currentChatConversationId ?? null;
|
|
1222
1239
|
if (convId) {
|
|
1223
|
-
const convMessages = await handleLoadMessages(convId, widgetId);
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1240
|
+
const convMessages = await handleLoadMessages(convId, widgetId) || [];
|
|
1241
|
+
const needle = content.trim();
|
|
1242
|
+
let matchIndex = -1;
|
|
1243
|
+
for (let i = convMessages.length - 1; i >= 0; i--) {
|
|
1244
|
+
const message = convMessages[i];
|
|
1245
|
+
const type = message.messageType || message.message_type;
|
|
1246
|
+
const text = String(message.messageContent || message.message_content || '').trim();
|
|
1247
|
+
if (type === 'user' && text === needle) {
|
|
1248
|
+
matchIndex = i;
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (matchIndex >= 0) {
|
|
1253
|
+
forkUserMessage = convMessages.slice(matchIndex + 1).some((message) =>
|
|
1254
|
+
(message.messageType || message.message_type) === 'assistant'
|
|
1255
|
+
&& messageTextHasCommit(message.messageContent || message.message_content),
|
|
1256
|
+
);
|
|
1257
|
+
if (!forkUserMessage) {
|
|
1258
|
+
retryFileAttachments = (convMessages[matchIndex].attachments || [])
|
|
1259
|
+
.filter((attachment) => attachment.url && /^https?:\/\//i.test(attachment.url))
|
|
1260
|
+
.filter((attachment) => !/design-screenshot/i.test(attachment.file_name || attachment.fileName || ''))
|
|
1261
|
+
.map((attachment) => ({
|
|
1262
|
+
url: attachment.url,
|
|
1263
|
+
file_type: attachment.file_type || attachment.fileType || 'application/octet-stream',
|
|
1264
|
+
file_name: attachment.file_name || attachment.fileName || null,
|
|
1265
|
+
file_size: attachment.file_size || attachment.fileSize || null,
|
|
1266
|
+
}));
|
|
1267
|
+
}
|
|
1238
1268
|
}
|
|
1239
1269
|
}
|
|
1240
1270
|
} catch (retryAttErr) {
|
|
1241
|
-
console.warn('[Chat→Retry] Could not load
|
|
1271
|
+
console.warn('[Chat→Retry] Could not load the prompt turn for retry:', retryAttErr);
|
|
1242
1272
|
}
|
|
1243
1273
|
}
|
|
1274
|
+
if (forkUserMessage) {
|
|
1275
|
+
panelChat?.addUserMessage?.(content);
|
|
1276
|
+
}
|
|
1244
1277
|
|
|
1245
1278
|
await generateCode({
|
|
1246
1279
|
prompt: content,
|
|
1247
1280
|
fromChat: true,
|
|
1248
1281
|
widgetId,
|
|
1249
1282
|
forceRecreateRepo: true,
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1283
|
+
forkUserMessage,
|
|
1284
|
+
attachmentIds: forkUserMessage ? undefined : (draftUploadIds.length > 0 ? draftUploadIds : undefined),
|
|
1285
|
+
promptImagesOverride: forkUserMessage ? [] : (draftImageUrls.length > 0 ? draftImageUrls : undefined),
|
|
1286
|
+
persistFileAttachments: !forkUserMessage && retryFileAttachments.length > 0 ? retryFileAttachments : undefined,
|
|
1253
1287
|
});
|
|
1254
1288
|
}
|
|
1255
1289
|
|
|
@@ -1532,6 +1566,7 @@
|
|
|
1532
1566
|
file_size?: number | null;
|
|
1533
1567
|
display_order?: number;
|
|
1534
1568
|
}>,
|
|
1569
|
+
forkUserMessage = false,
|
|
1535
1570
|
): Promise<void> {
|
|
1536
1571
|
if (!conversationsClient || !messagesClient) return;
|
|
1537
1572
|
try {
|
|
@@ -1552,6 +1587,8 @@
|
|
|
1552
1587
|
convId = conv?.id;
|
|
1553
1588
|
}
|
|
1554
1589
|
if (!convId) return;
|
|
1590
|
+
// The new bubble was already saved by addUserMessage. Do not patch the previous turn with the sketch.
|
|
1591
|
+
if (forkUserMessage) return;
|
|
1555
1592
|
|
|
1556
1593
|
const persistImages = uniquePersistImageUrls([
|
|
1557
1594
|
...(imageUrls || []),
|
|
@@ -8288,6 +8325,8 @@ Requirements:
|
|
|
8288
8325
|
skipIntentGate?: boolean;
|
|
8289
8326
|
/** The tapped chip's structured intent, declared so the server measures the chip conversion without matching copy. */
|
|
8290
8327
|
chipIntent?: ClarificationSuggestion['intent'];
|
|
8328
|
+
/** Successful retry: a new user row already exists. Do not attach images to the previous turn. */
|
|
8329
|
+
forkUserMessage?: boolean;
|
|
8291
8330
|
/** Chat files (PDF, video, images) to store on the user message before codegen. */
|
|
8292
8331
|
persistFileAttachments?: Array<{
|
|
8293
8332
|
url?: string;
|
|
@@ -8545,8 +8584,9 @@ Requirements:
|
|
|
8545
8584
|
}
|
|
8546
8585
|
|
|
8547
8586
|
// Prepare images for the request (if any) — prefer IDs/URLs captured before clearDraft
|
|
8548
|
-
let panelPromptImages =
|
|
8549
|
-
|
|
8587
|
+
let panelPromptImages = options.forkUserMessage
|
|
8588
|
+
? []
|
|
8589
|
+
: options.promptImagesOverride && options.promptImagesOverride.length > 0
|
|
8550
8590
|
? options.promptImagesOverride
|
|
8551
8591
|
: generationWidgetId === focusedPanelWidgetId
|
|
8552
8592
|
? promptImages
|
|
@@ -8556,12 +8596,14 @@ Requirements:
|
|
|
8556
8596
|
panelPromptImages = panelPromptImages.filter(
|
|
8557
8597
|
(url) => typeof url === 'string' && (url.startsWith('data:image/') || /^https?:\/\//i.test(url)) && url.length > 20,
|
|
8558
8598
|
);
|
|
8559
|
-
const chatAttachmentUrls = panelChat?.getAttachmentDataUrls?.() ?? [];
|
|
8560
|
-
const chatUploadIds =
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8599
|
+
const chatAttachmentUrls = options.forkUserMessage ? [] : (panelChat?.getAttachmentDataUrls?.() ?? []);
|
|
8600
|
+
const chatUploadIds = options.forkUserMessage
|
|
8601
|
+
? []
|
|
8602
|
+
: [
|
|
8603
|
+
...(options.attachmentIds || []),
|
|
8604
|
+
...(panelChat?.getAttachmentUploadIds?.() ?? []),
|
|
8605
|
+
].filter((id, index, arr) => id && arr.indexOf(id) === index);
|
|
8606
|
+
if (!options.forkUserMessage && chatAttachmentUrls.length > 0 && !(options.promptImagesOverride?.length)) {
|
|
8565
8607
|
const fromChat = chatAttachmentUrls.filter(
|
|
8566
8608
|
(url) => typeof url === 'string' && (url.startsWith('data:image/') || /^https?:\/\//i.test(url)) && url.length > 20,
|
|
8567
8609
|
);
|
|
@@ -8617,7 +8659,7 @@ Requirements:
|
|
|
8617
8659
|
duration: 8000
|
|
8618
8660
|
});
|
|
8619
8661
|
}
|
|
8620
|
-
} else if (promptImpliesVisionReference(prompt)) {
|
|
8662
|
+
} else if (promptImpliesVisionReference(prompt) && !options.forkUserMessage) {
|
|
8621
8663
|
showToast('warning', 'Generare fără imagine', {
|
|
8622
8664
|
description:
|
|
8623
8665
|
'Request-ul merge fără vision (DeepSeek etc.). Atașează PNG/JPEG în chat înainte de Send.',
|
|
@@ -8633,8 +8675,9 @@ Requirements:
|
|
|
8633
8675
|
await ensureCodegenUserMessagePersisted(
|
|
8634
8676
|
generationWidgetId,
|
|
8635
8677
|
prompt,
|
|
8636
|
-
persistImageUrls,
|
|
8637
|
-
options.persistFileAttachments,
|
|
8678
|
+
options.forkUserMessage ? [] : persistImageUrls,
|
|
8679
|
+
options.forkUserMessage ? undefined : options.persistFileAttachments,
|
|
8680
|
+
!!options.forkUserMessage,
|
|
8638
8681
|
);
|
|
8639
8682
|
|
|
8640
8683
|
// Clear unsent draft/attachments once payload is captured — prevents restore on widget return
|
|
@@ -8885,14 +8928,63 @@ Requirements:
|
|
|
8885
8928
|
if (generationWidgetId) {
|
|
8886
8929
|
const { [generationWidgetId]: _resolved, ...restResolved } = shapeHistoryResolvedByWidgetId;
|
|
8887
8930
|
shapeHistoryResolvedByWidgetId = restResolved;
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8931
|
+
const alignWidgetId = generationWidgetId;
|
|
8932
|
+
const alignCommitSha = completedCommitSha;
|
|
8933
|
+
const alignGeneratedVersion = (attempt: number) => {
|
|
8934
|
+
void loadPublishHistoryForPanel(alignWidgetId, { silent: true }).then(() => {
|
|
8935
|
+
const versionId = findVersionIdMatchingCommit(alignWidgetId, alignCommitSha);
|
|
8936
|
+
const matchedByCommit = !!alignCommitSha
|
|
8937
|
+
&& !!versionId
|
|
8938
|
+
&& getShapePublishHistory(alignWidgetId).some((row) =>
|
|
8939
|
+
versionId === `v:${Number(row.version)}`
|
|
8940
|
+
&& commitsMatch(row?.commitSha ?? row?.commit_sha, alignCommitSha),
|
|
8941
|
+
);
|
|
8942
|
+
if (!matchedByCommit && alignCommitSha && attempt < 4) {
|
|
8943
|
+
setTimeout(() => alignGeneratedVersion(attempt + 1), 1500);
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
if (!versionId) return;
|
|
8947
|
+
if (restoredViewingVersionByWidgetId[alignWidgetId] != null) {
|
|
8948
|
+
const { [alignWidgetId]: _clearedRestore, ...restRestored } = restoredViewingVersionByWidgetId;
|
|
8949
|
+
restoredViewingVersionByWidgetId = restRestored;
|
|
8950
|
+
}
|
|
8951
|
+
const menuNow = dropdownVersionByWidgetId[alignWidgetId];
|
|
8952
|
+
const sourceNow = shapePreviewSourceByWidgetId[alignWidgetId];
|
|
8953
|
+
const selectedRow = getShapePublishHistory(alignWidgetId)
|
|
8954
|
+
.find((row) => `v:${Number(row.version)}` === menuNow);
|
|
8955
|
+
if (
|
|
8956
|
+
menuNow?.startsWith('v:')
|
|
8957
|
+
&& menuNow === sourceNow
|
|
8958
|
+
&& alignCommitSha
|
|
8959
|
+
&& !commitsMatch(selectedRow?.commitSha ?? selectedRow?.commit_sha, alignCommitSha)
|
|
8960
|
+
) {
|
|
8961
|
+
return;
|
|
8962
|
+
}
|
|
8963
|
+
selectVersionInDropdowns(alignWidgetId, versionId);
|
|
8964
|
+
shapePreviewSourceByWidgetId = {
|
|
8965
|
+
...shapePreviewSourceByWidgetId,
|
|
8966
|
+
[alignWidgetId]: versionId,
|
|
8967
|
+
};
|
|
8968
|
+
syncWidgetShapeLivePreviews();
|
|
8969
|
+
panelDisplayRevision += 1;
|
|
8970
|
+
if (versionUsesSharedHeadPreview(alignWidgetId, versionId)) {
|
|
8971
|
+
const headUrl = resolveWidgetShapeLivePreviewUrl(alignWidgetId);
|
|
8972
|
+
getWidgetDetailsRef(alignWidgetId)?.showLiveWorkerPreview?.(headUrl);
|
|
8973
|
+
widgetShapeLivePreviewsRef?.requestReload?.(alignWidgetId);
|
|
8974
|
+
} else {
|
|
8975
|
+
const versionNumber = Number(versionId.slice(2));
|
|
8976
|
+
if (Number.isFinite(versionNumber)) {
|
|
8977
|
+
void ensureWidgetShapeVersionPreview(alignWidgetId, versionNumber);
|
|
8978
|
+
}
|
|
8893
8979
|
}
|
|
8980
|
+
console.log('[VersionPreview] Aligned dropdown with generated commit', {
|
|
8981
|
+
widgetId: alignWidgetId.substring(0, 8),
|
|
8982
|
+
versionId,
|
|
8983
|
+
commit: alignCommitSha?.substring(0, 8) ?? null,
|
|
8984
|
+
});
|
|
8894
8985
|
});
|
|
8895
|
-
}
|
|
8986
|
+
};
|
|
8987
|
+
setTimeout(() => alignGeneratedVersion(0), 1200);
|
|
8896
8988
|
}
|
|
8897
8989
|
|
|
8898
8990
|
// Always reconcile GitLab HEAD after codegen (metadata.commitId can be missing/stale).
|
|
@@ -9053,10 +9145,8 @@ Requirements:
|
|
|
9053
9145
|
await activePanelRef?.refreshPreview?.({ withRetry: true, skipIfAlreadyShowing: true });
|
|
9054
9146
|
}
|
|
9055
9147
|
activePanelRef?.setPreviewBuildError(null);
|
|
9056
|
-
// TODO(tomorrow): Dupa o generare noua, sincronizarea versiunii in dropdown si istoric se desincronizeaza.
|
|
9057
|
-
// Trebuie armonizata selectia automata a noii versiuni draft (vN) create in istoric,
|
|
9058
|
-
// actualizat trigger-ul pe canvas si in chat la noul numar de versiune si pastrata coerenta intre preview si dropdown.
|
|
9059
9148
|
// Show Worker preview on the canvas shape (not the previous Live CDN artifact).
|
|
9149
|
+
// History alignment below moves the dropdown onto the commit this build belongs to.
|
|
9060
9150
|
shapePreviewSourceByWidgetId = {
|
|
9061
9151
|
...shapePreviewSourceByWidgetId,
|
|
9062
9152
|
[generationWidgetId]: 'preview',
|
|
@@ -9468,10 +9558,18 @@ Requirements:
|
|
|
9468
9558
|
isPublishing = false;
|
|
9469
9559
|
publishingWidgetId = null;
|
|
9470
9560
|
setCrossTabPublishing(false, capturedWidgetId);
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9561
|
+
const publishErrorText = normalizeErrorMessage(err) || 'An unexpected error occurred. Please try again.';
|
|
9562
|
+
if (isPlanLimitMessage(publishErrorText) && capturedWidgetId) {
|
|
9563
|
+
publishPlanMessageByWidgetId = {
|
|
9564
|
+
...publishPlanMessageByWidgetId,
|
|
9565
|
+
[capturedWidgetId]: publishErrorText,
|
|
9566
|
+
};
|
|
9567
|
+
} else {
|
|
9568
|
+
showToast('error', 'Publish failed', {
|
|
9569
|
+
description: publishErrorText,
|
|
9570
|
+
duration: 8000
|
|
9571
|
+
});
|
|
9572
|
+
}
|
|
9475
9573
|
}
|
|
9476
9574
|
// isPublishing stays true until widget_publish_status WebSocket event arrives (success or failure)
|
|
9477
9575
|
}
|
|
@@ -10495,6 +10593,15 @@ Requirements:
|
|
|
10495
10593
|
function getPanelEditPreviewUrl(widgetId: string): string | null {
|
|
10496
10594
|
const ps = getPanelDisplayState(widgetId);
|
|
10497
10595
|
const source = shapePreviewSourceByWidgetId[widgetId];
|
|
10596
|
+
// Head draft and the post-codegen 'preview' source are the Worker build.
|
|
10597
|
+
// Falling through here used to hand the panel the last published CDN file
|
|
10598
|
+
// while the dropdown already said the new draft.
|
|
10599
|
+
if (source === 'preview' || (source && versionUsesSharedHeadPreview(widgetId, source))) {
|
|
10600
|
+
const hasCode = !!(ps.lastCommitId || loadCommitIdForWidget(widgetId));
|
|
10601
|
+
if (hasCode && source !== 'live') {
|
|
10602
|
+
return `${widgetBuilderPreviewBaseUrl()}/preview/${widgetId}`;
|
|
10603
|
+
}
|
|
10604
|
+
}
|
|
10498
10605
|
if (source && source.startsWith('v:')) {
|
|
10499
10606
|
const version = parseInt(source.slice(2), 10);
|
|
10500
10607
|
if (Number.isFinite(version)) {
|
|
@@ -10631,6 +10738,45 @@ Requirements:
|
|
|
10631
10738
|
return draftVersions.length > 0 ? Math.max(...draftVersions) : null;
|
|
10632
10739
|
}
|
|
10633
10740
|
|
|
10741
|
+
/** True when this draft row's commit is still the repo HEAD the shared Worker preview serves. */
|
|
10742
|
+
function draftCommitMatchesRepoHead(widgetId: string, record: { commitSha?: string | null; commit_sha?: string | null } | null | undefined): boolean {
|
|
10743
|
+
const draftCommitSha = normalizeCommitSha(record?.commitSha ?? record?.commit_sha ?? null);
|
|
10744
|
+
const headCommitSha = normalizeCommitSha(
|
|
10745
|
+
getPanelDisplayState(widgetId).lastCommitId ?? loadCommitIdForWidget(widgetId),
|
|
10746
|
+
);
|
|
10747
|
+
return !draftCommitSha || !headCommitSha || commitsMatch(draftCommitSha, headCommitSha);
|
|
10748
|
+
}
|
|
10749
|
+
|
|
10750
|
+
/**
|
|
10751
|
+
* Shared `/preview/{widgetId}` is only the selected row when that row is the
|
|
10752
|
+
* newest draft and its commit is still HEAD. Every other version needs its
|
|
10753
|
+
* own build — otherwise the dropdown says vN while the iframe shows HEAD.
|
|
10754
|
+
*/
|
|
10755
|
+
function versionUsesSharedHeadPreview(widgetId: string, versionId: string | null | undefined): boolean {
|
|
10756
|
+
if (!versionId || versionId === 'preview' || versionId === 'live') return versionId !== 'live';
|
|
10757
|
+
if (!versionId.startsWith('v:')) return false;
|
|
10758
|
+
const version = parseInt(versionId.slice(2), 10);
|
|
10759
|
+
if (!Number.isFinite(version)) return false;
|
|
10760
|
+
const record = getShapePublishHistory(widgetId).find((row) => Number(row.version) === version);
|
|
10761
|
+
if (record?.publishedAt ?? record?.published_at) return false;
|
|
10762
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10763
|
+
if (newestDraft == null || version !== newestDraft) return false;
|
|
10764
|
+
if (restoredViewingVersionByWidgetId[widgetId] != null) return false;
|
|
10765
|
+
return draftCommitMatchesRepoHead(widgetId, record);
|
|
10766
|
+
}
|
|
10767
|
+
|
|
10768
|
+
function findVersionIdMatchingCommit(widgetId: string, commitSha: string | null): string | null {
|
|
10769
|
+
const target = normalizeCommitSha(commitSha);
|
|
10770
|
+
const history = getShapePublishHistory(widgetId);
|
|
10771
|
+
if (target) {
|
|
10772
|
+
const match = history.find((row) => commitsMatch(row?.commitSha ?? row?.commit_sha, target));
|
|
10773
|
+
const version = Number(match?.version);
|
|
10774
|
+
if (Number.isFinite(version)) return `v:${version}`;
|
|
10775
|
+
}
|
|
10776
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10777
|
+
return newestDraft != null ? `v:${newestDraft}` : null;
|
|
10778
|
+
}
|
|
10779
|
+
|
|
10634
10780
|
function resolveWidgetShapeLivePreviewUrl(widgetId: string): string | null {
|
|
10635
10781
|
if (!widgetId) return null;
|
|
10636
10782
|
const source = shapePreviewSourceByWidgetId[widgetId] || 'live';
|
|
@@ -10665,11 +10811,15 @@ Requirements:
|
|
|
10665
10811
|
// `/preview/{widgetId}` artifact then serves the restored code under the
|
|
10666
10812
|
// newest version's number — ask for a commit-specific build instead.
|
|
10667
10813
|
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10668
|
-
const
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10814
|
+
const draftIsRepoHead = draftCommitMatchesRepoHead(widgetId, record);
|
|
10815
|
+
if (
|
|
10816
|
+
!isPublished
|
|
10817
|
+
&& newestDraft != null
|
|
10818
|
+
&& version === newestDraft
|
|
10819
|
+
&& draftIsRepoHead
|
|
10820
|
+
&& workerUrl
|
|
10821
|
+
&& restoredViewingVersionByWidgetId[widgetId] == null
|
|
10822
|
+
) {
|
|
10673
10823
|
return workerUrl;
|
|
10674
10824
|
}
|
|
10675
10825
|
return resolveWidgetShapeVersionPreviewState(widgetId)?.url ?? null;
|
|
@@ -10852,10 +11002,18 @@ Requirements:
|
|
|
10852
11002
|
const isPublished = !!(record?.publishedAt ?? record?.published_at);
|
|
10853
11003
|
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
10854
11004
|
const workerUrl = resolveWidgetShapeLivePreviewUrl(widgetId);
|
|
10855
|
-
const viewingHeadDraft = !isPublished
|
|
10856
|
-
|
|
11005
|
+
const viewingHeadDraft = !isPublished
|
|
11006
|
+
&& newestDraft != null
|
|
11007
|
+
&& version === newestDraft
|
|
11008
|
+
&& draftCommitMatchesRepoHead(widgetId, record)
|
|
11009
|
+
&& !!workerUrl
|
|
11010
|
+
&& restoredViewingVersionByWidgetId[widgetId] == null;
|
|
11011
|
+
if (viewingHeadDraft && workerUrl) {
|
|
10857
11012
|
const detailsRef = getWidgetDetailsRef(widgetId);
|
|
10858
|
-
|
|
11013
|
+
// Same Worker URL as the previous draft: the shape iframe does not
|
|
11014
|
+
// remount unless the cache-bust token changes.
|
|
11015
|
+
detailsRef?.showLiveWorkerPreview?.(workerUrl);
|
|
11016
|
+
widgetShapeLivePreviewsRef?.requestReload?.(widgetId);
|
|
10859
11017
|
} else {
|
|
10860
11018
|
void ensureWidgetShapeVersionPreview(widgetId, version);
|
|
10861
11019
|
}
|
|
@@ -10992,6 +11150,7 @@ Requirements:
|
|
|
10992
11150
|
*/
|
|
10993
11151
|
let versionLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
10994
11152
|
let versionPromptLabelsByWidgetId: Record<string, Record<string, string>> = {};
|
|
11153
|
+
let versionTimelineByWidgetId: Record<string, Array<{ version: number; sha: string; prompt: string }>> = {};
|
|
10995
11154
|
let viewingVersionByWidgetId: Record<string, number | null> = {};
|
|
10996
11155
|
$: {
|
|
10997
11156
|
void shapePreviewSourceByWidgetId;
|
|
@@ -11002,16 +11161,19 @@ Requirements:
|
|
|
11002
11161
|
const nextHeads: Record<string, string | null> = {};
|
|
11003
11162
|
const nextLabels: Record<string, Record<string, string>> = {};
|
|
11004
11163
|
const nextPrompts: Record<string, Record<string, string>> = {};
|
|
11164
|
+
const nextTimeline: Record<string, Array<{ version: number; sha: string; prompt: string }>> = {};
|
|
11005
11165
|
const nextViewing: Record<string, number | null> = {};
|
|
11006
11166
|
for (const widgetId of openWidgetPanelIds) {
|
|
11007
11167
|
nextHeads[widgetId] = resolveViewingCommitShaForPanel(widgetId);
|
|
11008
11168
|
nextLabels[widgetId] = buildVersionLabelsForPanel(widgetId);
|
|
11009
11169
|
nextPrompts[widgetId] = buildVersionPromptLabelsForPanel(widgetId);
|
|
11170
|
+
nextTimeline[widgetId] = buildVersionTimelineForPanel(widgetId);
|
|
11010
11171
|
nextViewing[widgetId] = viewingVersionNumberForPanel(widgetId);
|
|
11011
11172
|
}
|
|
11012
11173
|
headCommitShaByWidgetId = nextHeads;
|
|
11013
11174
|
versionLabelsByWidgetId = nextLabels;
|
|
11014
11175
|
versionPromptLabelsByWidgetId = nextPrompts;
|
|
11176
|
+
versionTimelineByWidgetId = nextTimeline;
|
|
11015
11177
|
viewingVersionByWidgetId = nextViewing;
|
|
11016
11178
|
}
|
|
11017
11179
|
|
|
@@ -11049,6 +11211,20 @@ Requirements:
|
|
|
11049
11211
|
return text || null;
|
|
11050
11212
|
}
|
|
11051
11213
|
|
|
11214
|
+
function buildVersionTimelineForPanel(widgetId: string): Array<{ version: number; sha: string; prompt: string }> {
|
|
11215
|
+
const ps = getPanelDisplayState(widgetId);
|
|
11216
|
+
const history = ps.publishHistory?.length ? ps.publishHistory : publishHistory;
|
|
11217
|
+
const timeline: Array<{ version: number; sha: string; prompt: string }> = [];
|
|
11218
|
+
for (const record of history || []) {
|
|
11219
|
+
if (record?.version == null) continue;
|
|
11220
|
+
const sha = normalizeCommitSha(record?.commitSha ?? record?.commit_sha);
|
|
11221
|
+
const prompt = versionPromptKey(record?.prompt);
|
|
11222
|
+
if (!sha || !prompt) continue;
|
|
11223
|
+
timeline.push({ version: Number(record.version), sha, prompt });
|
|
11224
|
+
}
|
|
11225
|
+
return timeline;
|
|
11226
|
+
}
|
|
11227
|
+
|
|
11052
11228
|
/** User prompt → "Version N", for turns whose commit line was never saved. */
|
|
11053
11229
|
function buildVersionPromptLabelsForPanel(widgetId: string): Record<string, string> {
|
|
11054
11230
|
const labels: Record<string, string> = {};
|
|
@@ -12521,15 +12697,24 @@ $: if ($dynamicWorkerPreviewStatus.widgetId && $dynamicWorkerPreviewStatus.previ
|
|
|
12521
12697
|
const wsEventKey = `${wsReadyWidgetId}:${$dynamicWorkerPreviewStatus.previewUrl}:${$dynamicWorkerPreviewStatus.timestamp ?? ''}`;
|
|
12522
12698
|
if (wsEventKey !== lastWsWorkerPreviewEventKey) {
|
|
12523
12699
|
lastWsWorkerPreviewEventKey = wsEventKey;
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12700
|
+
const selectedVersion = dropdownVersionByWidgetId[wsReadyWidgetId]
|
|
12701
|
+
|| shapePreviewSourceByWidgetId[wsReadyWidgetId];
|
|
12702
|
+
const keepHistoricalSelection = !generatingWidgetIds[wsReadyWidgetId]
|
|
12703
|
+
&& !!selectedVersion?.startsWith('v:')
|
|
12704
|
+
&& !versionUsesSharedHeadPreview(wsReadyWidgetId, selectedVersion);
|
|
12705
|
+
if (keepHistoricalSelection) {
|
|
12706
|
+
console.log('[CreatorApp] Historical version stays selected; shared HEAD build ignored', wsReadyWidgetId.substring(0, 8));
|
|
12530
12707
|
} else {
|
|
12531
|
-
|
|
12532
|
-
|
|
12708
|
+
shapePreviewSourceByWidgetId = {
|
|
12709
|
+
...shapePreviewSourceByWidgetId,
|
|
12710
|
+
[wsReadyWidgetId]: 'preview',
|
|
12711
|
+
};
|
|
12712
|
+
if (codegenCaptureBlocked()) {
|
|
12713
|
+
console.log('[CreatorApp] Defer canvas Worker preview reload until codegen finishes', wsReadyWidgetId.substring(0, 8));
|
|
12714
|
+
} else {
|
|
12715
|
+
syncWidgetShapeLivePreviews();
|
|
12716
|
+
widgetShapeLivePreviewsRef?.requestReload?.(wsReadyWidgetId);
|
|
12717
|
+
}
|
|
12533
12718
|
}
|
|
12534
12719
|
}
|
|
12535
12720
|
}
|
|
@@ -15841,10 +16026,47 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15841
16026
|
{/if}
|
|
15842
16027
|
</div>
|
|
15843
16028
|
</div>
|
|
16029
|
+
{#if publishPlanMessageByWidgetId[panelWidgetId]}
|
|
16030
|
+
<div class="publish-plan-notice p-3 bg-red-50 border border-red-200" style="border-radius: 0;">
|
|
16031
|
+
<p class="text-xs text-red-700 whitespace-pre-wrap">{publishPlanMessageByWidgetId[panelWidgetId]}</p>
|
|
16032
|
+
<div class="flex gap-2 mt-3 justify-center">
|
|
16033
|
+
<Button
|
|
16034
|
+
onclick={openUpgradePlans}
|
|
16035
|
+
variant="default"
|
|
16036
|
+
size="sm"
|
|
16037
|
+
class="bg-red-600 hover:bg-red-700 text-white rounded-lg"
|
|
16038
|
+
>
|
|
16039
|
+
Upgrade
|
|
16040
|
+
</Button>
|
|
16041
|
+
<Button
|
|
16042
|
+
onclick={() => {
|
|
16043
|
+
const next = { ...publishPlanMessageByWidgetId };
|
|
16044
|
+
delete next[panelWidgetId];
|
|
16045
|
+
publishPlanMessageByWidgetId = next;
|
|
16046
|
+
}}
|
|
16047
|
+
variant="outline"
|
|
16048
|
+
size="sm"
|
|
16049
|
+
class="border-gray-300 text-gray-600 hover:bg-gray-50 rounded-lg"
|
|
16050
|
+
>
|
|
16051
|
+
Dismiss
|
|
16052
|
+
</Button>
|
|
16053
|
+
</div>
|
|
16054
|
+
</div>
|
|
16055
|
+
{/if}
|
|
15844
16056
|
{#if ps.lastGenerationFailed}
|
|
15845
16057
|
<div class="p-3 bg-red-50 border border-red-200" style="border-radius: 0;">
|
|
15846
16058
|
<p class="text-xs text-red-700 whitespace-pre-wrap">{ps.lastGenerationError}</p>
|
|
15847
16059
|
<div class="flex gap-2 mt-3 justify-center">
|
|
16060
|
+
{#if isPlanLimitMessage(ps.lastGenerationError)}
|
|
16061
|
+
<Button
|
|
16062
|
+
onclick={openUpgradePlans}
|
|
16063
|
+
variant="default"
|
|
16064
|
+
size="sm"
|
|
16065
|
+
class="bg-red-600 hover:bg-red-700 text-white rounded-lg"
|
|
16066
|
+
>
|
|
16067
|
+
Upgrade
|
|
16068
|
+
</Button>
|
|
16069
|
+
{:else}
|
|
15848
16070
|
<Button
|
|
15849
16071
|
onclick={() => {
|
|
15850
16072
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
@@ -15859,6 +16081,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15859
16081
|
>
|
|
15860
16082
|
Retry
|
|
15861
16083
|
</Button>
|
|
16084
|
+
{/if}
|
|
15862
16085
|
<Button
|
|
15863
16086
|
onclick={() => {
|
|
15864
16087
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
@@ -15934,6 +16157,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15934
16157
|
widgetHeadCommitSha={headCommitShaByWidgetId[panelWidgetId] || ps.lastCommitId}
|
|
15935
16158
|
versionLabelByCommitSha={versionLabelsByWidgetId[panelWidgetId] || {}}
|
|
15936
16159
|
versionLabelByPrompt={versionPromptLabelsByWidgetId[panelWidgetId] || {}}
|
|
16160
|
+
versionTimeline={versionTimelineByWidgetId[panelWidgetId] || []}
|
|
15937
16161
|
viewingVersion={viewingVersionByWidgetId[panelWidgetId] ?? null}
|
|
15938
16162
|
onSendMessage={(content, attachments) => handleChatSendMessage(content, attachments, panelWidgetId)}
|
|
15939
16163
|
onRetryMessage={(content) => handleChatRetryMessage(content, panelWidgetId)}
|
|
@@ -497,6 +497,20 @@
|
|
|
497
497
|
void forceReloadPreviewIframe(url);
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Shared Worker HEAD (`/preview/{widgetId}`). Do not pin it: a pin blocks the
|
|
502
|
+
* next build event, so the panel keeps the previous generation.
|
|
503
|
+
*/
|
|
504
|
+
export function showLiveWorkerPreview(url: string | null): void {
|
|
505
|
+
if (!url) return;
|
|
506
|
+
pinnedPublishedPreviewUrl = null;
|
|
507
|
+
lastWsPreviewSignature = null;
|
|
508
|
+
publishedArtifactReachable = true;
|
|
509
|
+
_lastEditPreviewBase = null;
|
|
510
|
+
_lastEmbedPreviewBase = null;
|
|
511
|
+
void forceReloadPreviewIframe(url);
|
|
512
|
+
}
|
|
513
|
+
|
|
500
514
|
export function finishCompositionHydration(): void {
|
|
501
515
|
if (compositionHydrationTimeout) {
|
|
502
516
|
clearTimeout(compositionHydrationTimeout);
|
|
@@ -106,6 +106,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
106
106
|
*/ showCompositionSteps?: boolean;
|
|
107
107
|
beginCompositionHydration?: () => void;
|
|
108
108
|
showPublishedArtifact?: (url: string | null) => void;
|
|
109
|
+
showLiveWorkerPreview?: (url: string | null) => void;
|
|
109
110
|
finishCompositionHydration?: () => void;
|
|
110
111
|
markCodegenPreviewFresh?: () => void;
|
|
111
112
|
clearCodegenPreviewFresh?: () => void;
|
|
@@ -207,6 +208,7 @@ declare const WidgetDetails: $$__sveltets_2_IsomorphicComponent<{
|
|
|
207
208
|
debugModeAvailable: boolean;
|
|
208
209
|
beginCompositionHydration: () => void;
|
|
209
210
|
showPublishedArtifact: (url: string | null) => void;
|
|
211
|
+
showLiveWorkerPreview: (url: string | null) => void;
|
|
210
212
|
finishCompositionHydration: () => void;
|
|
211
213
|
markCodegenPreviewFresh: () => void;
|
|
212
214
|
clearCodegenPreviewFresh: () => void;
|
|
@@ -205,19 +205,25 @@
|
|
|
205
205
|
const coalescedReloadTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
206
206
|
|
|
207
207
|
export function requestReload(widgetId: string): void {
|
|
208
|
-
if (isGenerating) return;
|
|
209
208
|
const now = Date.now();
|
|
210
209
|
const sinceLast = now - (lastReloadAtByWidgetId[widgetId] || 0);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
// A second call inside the window used to be dropped when a timer already
|
|
211
|
+
// existed, so the codegen reload (the one that matches the new commit)
|
|
212
|
+
// never ran and the shape kept the previous build. Replace the timer so
|
|
213
|
+
// the later request wins. While generating, wait and try again.
|
|
214
|
+
if (isGenerating || sinceLast < RELOAD_COALESCE_MS) {
|
|
215
|
+
const existing = coalescedReloadTimers.get(widgetId);
|
|
216
|
+
if (existing) clearTimeout(existing);
|
|
217
|
+
const wait = isGenerating
|
|
218
|
+
? RELOAD_COALESCE_MS
|
|
219
|
+
: Math.max(0, RELOAD_COALESCE_MS - sinceLast);
|
|
220
|
+
coalescedReloadTimers.set(
|
|
221
|
+
widgetId,
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
coalescedReloadTimers.delete(widgetId);
|
|
224
|
+
requestReload(widgetId);
|
|
225
|
+
}, wait),
|
|
226
|
+
);
|
|
221
227
|
return;
|
|
222
228
|
}
|
|
223
229
|
const coalesced = coalescedReloadTimers.get(widgetId);
|