@widgetic/creator 0.3.71 → 0.3.73
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 +882 -78
- package/dist/components/BrandKitPanel.svelte +556 -0
- package/dist/components/BrandKitPanel.svelte.d.ts +32 -0
- package/dist/components/DesignQuickEdit.svelte +218 -0
- package/dist/components/DesignQuickEdit.svelte.d.ts +33 -0
- package/dist/components/FileUrlSourceControl.svelte +69 -0
- package/dist/components/FileUrlSourceControl.svelte.d.ts +15 -0
- package/dist/components/VersionDropdownRow.svelte +86 -1
- package/dist/components/VersionDropdownRow.svelte.d.ts +10 -0
- package/dist/components/WidgetDetails.svelte +19 -2
- package/dist/components/WidgetShapeLivePreviews.svelte +8 -3
- package/dist/retiredSurface.test.d.ts +1 -0
- package/dist/retiredSurface.test.js +113 -0
- package/dist/stores/websocketStore.d.ts +3 -0
- package/dist/stores/websocketStore.js +4 -0
- package/dist/utils/canvasShapeSnapClient.d.ts +27 -0
- package/dist/utils/canvasShapeSnapClient.js +67 -0
- package/dist/utils/designIntentJevClient.d.ts +32 -0
- package/dist/utils/designIntentJevClient.js +51 -0
- package/dist/utils/designIntentResolver.d.ts +107 -0
- package/dist/utils/designIntentResolver.js +509 -0
- package/dist/utils/designIntentResolver.test.d.ts +1 -0
- package/dist/utils/designIntentResolver.test.js +246 -0
- package/dist/utils/designIntentTelemetry.d.ts +176 -0
- package/dist/utils/designIntentTelemetry.js +474 -0
- package/dist/utils/designIntentTelemetry.test.d.ts +1 -0
- package/dist/utils/designIntentTelemetry.test.js +385 -0
- package/dist/utils/designQuickEditValues.d.ts +51 -0
- package/dist/utils/designQuickEditValues.js +77 -0
- package/dist/utils/designQuickEditValues.test.d.ts +1 -0
- package/dist/utils/designQuickEditValues.test.js +100 -0
- package/dist/utils/pinScrollToBottom.js +9 -2
- package/package.json +2 -2
package/dist/CreatorApp.svelte
CHANGED
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
MessagesApi,
|
|
26
26
|
CompositionsApi,
|
|
27
27
|
UploadsApi,
|
|
28
|
-
|
|
28
|
+
UsersApi,
|
|
29
|
+
ResponseError,
|
|
30
|
+
isRetiredSurfaceError
|
|
29
31
|
} from '@widgetic/api-sdk';
|
|
30
32
|
import type {
|
|
31
33
|
Middleware,
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
AgentModelsGet200ResponseModelsInner
|
|
34
36
|
} from '@widgetic/api-sdk';
|
|
35
37
|
import { parseVersionPreviewError } from './utils/versionPreviewError.js';
|
|
38
|
+
import BrandKitPanel from './components/BrandKitPanel.svelte';
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Idempotency middleware (Phase 2 hardening): the gateway rejects
|
|
@@ -345,6 +348,9 @@
|
|
|
345
348
|
let messagesClient: MessagesApi;
|
|
346
349
|
let compositionsClient: CompositionsApi;
|
|
347
350
|
let uploadsClient: UploadsApi;
|
|
351
|
+
let usersClient: UsersApi;
|
|
352
|
+
/** Credits, and days left while the account is still on the free trial. */
|
|
353
|
+
let trialCounter: { credits: number; days: number | null; trialEnded: boolean } | null = null;
|
|
348
354
|
|
|
349
355
|
// Track which token the authenticated clients were built with
|
|
350
356
|
let authenticatedClientsToken: string | null = null;
|
|
@@ -390,6 +396,9 @@
|
|
|
390
396
|
uploadsClient = apiBasePath
|
|
391
397
|
? new UploadsApi({ basePath: apiBasePath, accessToken: token, middleware: [idempotencyKeyMiddleware] })
|
|
392
398
|
: new UploadsApi({ accessToken: token, middleware: [idempotencyKeyMiddleware] });
|
|
399
|
+
usersClient = apiBasePath
|
|
400
|
+
? new UsersApi({ basePath: apiBasePath, accessToken: token })
|
|
401
|
+
: new UsersApi({ accessToken: token });
|
|
393
402
|
|
|
394
403
|
authenticatedClientsToken = token;
|
|
395
404
|
try {
|
|
@@ -398,6 +407,41 @@
|
|
|
398
407
|
/* websocket store may not be ready yet */
|
|
399
408
|
}
|
|
400
409
|
syncFileBrowserUploadConfigForCreator();
|
|
410
|
+
void refreshTrialCounter();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Credits left and days left, from the same billing status the publish gate uses. */
|
|
414
|
+
async function refreshTrialCounter() {
|
|
415
|
+
if (!usersClient) return;
|
|
416
|
+
try {
|
|
417
|
+
const accepted = await usersClient.usersMeBillingGetRaw();
|
|
418
|
+
const body = await accepted.raw.json();
|
|
419
|
+
const data = (body && typeof body === 'object' && 'data' in body ? body.data : body) as {
|
|
420
|
+
trialActive?: boolean;
|
|
421
|
+
creditsAvailable?: number;
|
|
422
|
+
creditBalance?: number;
|
|
423
|
+
trialDaysLeft?: number | null;
|
|
424
|
+
subscription?: { plan_type?: string | null; planType?: string | null } | null;
|
|
425
|
+
} | null;
|
|
426
|
+
if (!data || (data.creditsAvailable == null && data.creditBalance == null && !data.trialActive)) {
|
|
427
|
+
trialCounter = null;
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const credits = Number(data.creditsAvailable ?? data.creditBalance ?? 0);
|
|
431
|
+
const planType = String(data.subscription?.plan_type || data.subscription?.planType || '').toLowerCase();
|
|
432
|
+
const paidPlan = planType === 'solo' || planType === 'teams' || planType === 'team';
|
|
433
|
+
const days = data.trialDaysLeft == null ? null : Number(data.trialDaysLeft);
|
|
434
|
+
trialCounter = {
|
|
435
|
+
credits: Number.isFinite(credits) ? Math.max(0, Math.round(credits)) : 0,
|
|
436
|
+
days: paidPlan || days == null || !Number.isFinite(days) ? null : Math.max(0, Math.round(days)),
|
|
437
|
+
trialEnded: !paidPlan && days === 0,
|
|
438
|
+
};
|
|
439
|
+
if (typeof window !== 'undefined') {
|
|
440
|
+
window.dispatchEvent(new CustomEvent('widgetic:trial-counter'));
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.warn('[Trial] Could not load the credit counter', error);
|
|
444
|
+
}
|
|
401
445
|
}
|
|
402
446
|
|
|
403
447
|
function applySiteSessionToCreator(): boolean {
|
|
@@ -476,9 +520,35 @@
|
|
|
476
520
|
import WidgetShapeLivePreviews from './components/WidgetShapeLivePreviews.svelte';
|
|
477
521
|
import HoverTooltip from './components/HoverTooltip.svelte';
|
|
478
522
|
import VersionDropdownRow from './components/VersionDropdownRow.svelte';
|
|
523
|
+
// Type-only: erased at compile time, so the component's code is still
|
|
524
|
+
// dynamically imported below — this line exists so the ref types below can
|
|
525
|
+
// derive from the component's REAL class instead of the `any` a dynamic
|
|
526
|
+
// `import('./components/WidgetDetails.svelte')` resolves to (its module
|
|
527
|
+
// type carries no instance shape, which silently re-opens the any-ref hole
|
|
528
|
+
// this block exists to close).
|
|
529
|
+
import type WidgetDetailsComponent from './components/WidgetDetails.svelte';
|
|
479
530
|
|
|
480
531
|
// WIDGET DETAILS COMPONENT — lazy-loaded to reduce peak build memory
|
|
481
|
-
|
|
532
|
+
//
|
|
533
|
+
// The component value and its module promise are **derived types**, not
|
|
534
|
+
// hand-written mirrors — the same contract canvasRef and
|
|
535
|
+
// widgetShapeLivePreviewsRef below carry. WidgetDetails.svelte is classic
|
|
536
|
+
// Svelte 4, so the instance type is `InstanceType` of its component class
|
|
537
|
+
// (imported type-only above — see the note there for why the dynamic import
|
|
538
|
+
// cannot supply this type). `WithoutIndexKeys` (declared with the canvas ref
|
|
539
|
+
// below) strips the string index signature the base plumbing inherits,
|
|
540
|
+
// which the falsification probe for this very file proved lets unknown-member
|
|
541
|
+
// calls compile. A renamed or removed `export function` on
|
|
542
|
+
// WidgetDetails.svelte is now a compile error at every stale CreatorApp call
|
|
543
|
+
// site instead of a silent runtime `undefined`; a new export appears here
|
|
544
|
+
// without any edit to this file. The values stay
|
|
545
|
+
// `| null`/`| undefined` because the module loads lazily — the
|
|
546
|
+
// optionality lives at the assignment, not in the type.
|
|
547
|
+
type WidgetDetailsInstance = WithoutIndexKeys<InstanceType<typeof WidgetDetailsComponent>>;
|
|
548
|
+
// The lazily-assigned value is the COMPONENT (constructor) — what
|
|
549
|
+
// `<svelte:component this={…}>` consumes — while the refs and
|
|
550
|
+
// `getWidgetDetailsRef` hand out INSTANCES. Two shapes, two types.
|
|
551
|
+
let WidgetDetails: typeof WidgetDetailsComponent | undefined = undefined;
|
|
482
552
|
let widgetDetailsRefs: Record<string, any> = {};
|
|
483
553
|
let openWidgetPanelIds: string[] = [];
|
|
484
554
|
/** Stable {#each} keys so draft → real widget does not remount Widget Details. */
|
|
@@ -495,27 +565,76 @@
|
|
|
495
565
|
// WidgetConversionPayload lives on the canvas root export (re-exported from
|
|
496
566
|
// ./canvas); the components barrel only carries the input controls. Type-only
|
|
497
567
|
// import, so the 10K-line Canvas.svelte still never loads.
|
|
498
|
-
import type {
|
|
568
|
+
import type {
|
|
569
|
+
WidgetConversionPayload,
|
|
570
|
+
StrokeRecognitionRequest,
|
|
571
|
+
StrokeRecognitionResult
|
|
572
|
+
} from '@widgetic/canvas';
|
|
499
573
|
|
|
500
574
|
// Chat module for code generation conversation UI
|
|
501
575
|
import { Chat, ContextType } from '@widgetic/chat';
|
|
502
576
|
import type { ClarificationSuggestion } from '@widgetic/chat';
|
|
577
|
+
import DesignQuickEdit from './components/DesignQuickEdit.svelte';
|
|
578
|
+
import { collectDesignProperties, resolveDesignIntent, toDesignControlRequest, type DesignControlRequest } from './utils/designIntentResolver';
|
|
579
|
+
import { isDesignCardAttached } from './utils/designQuickEditValues';
|
|
580
|
+
import { resolveDesignIntentViaJev } from './utils/designIntentJevClient';
|
|
581
|
+
import { recognizeStrokeViaJev } from './utils/canvasShapeSnapClient';
|
|
582
|
+
import {
|
|
583
|
+
recordDesignIntentTelemetry,
|
|
584
|
+
recordDesignIntentJevOutcome,
|
|
585
|
+
isJevCandidateBucket,
|
|
586
|
+
type DesignIntentClassification
|
|
587
|
+
} from './utils/designIntentTelemetry';
|
|
503
588
|
|
|
504
589
|
// Eagerly initiate canvas chunk fetching at script evaluation (parallel with DOM parsing)
|
|
505
590
|
const canvasModulePromise = typeof window !== 'undefined' ? import('@widgetic/canvas') : null;
|
|
506
591
|
|
|
507
|
-
let WidgeticCanvas:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
592
|
+
let WidgeticCanvas: typeof import('@widgetic/canvas').Canvas | null = null;
|
|
593
|
+
/**
|
|
594
|
+
* Pattern reference: `__docs/15-svelte4-component-ref-typing.md`.
|
|
595
|
+
*
|
|
596
|
+
* Drops the string-index keys a Svelte 4 component's instance type inherits
|
|
597
|
+
* from its prop plumbing, keeping only the class's own members. Without
|
|
598
|
+
* this, `canvasRef.anythingAtAll` type-checks and a mistyped method name
|
|
599
|
+
* slips through as a silent runtime `undefined`.
|
|
600
|
+
*/
|
|
601
|
+
type WithoutIndexKeys<T> = { [K in keyof T as string extends K ? never : K]: T[K] };
|
|
602
|
+
/**
|
|
603
|
+
* The canvas component instance, **derived from the component's own type**
|
|
604
|
+
* (`InstanceType` of the `@widgetic/canvas` `Canvas` class), not a
|
|
605
|
+
* hand-written mirror. This component is classic Svelte 4 — a
|
|
606
|
+
* `SvelteComponentTyped` subclass whose instance members are compiled
|
|
607
|
+
* `export function` getters — so the instance type is `InstanceType<…>`
|
|
608
|
+
* (the Svelte 5 call-signature components elsewhere in this file use
|
|
609
|
+
* `ReturnType<…>`; same idea, different component authoring era). A new
|
|
610
|
+
* `export function` on Canvas.svelte appears here after a dist rebuild,
|
|
611
|
+
* and a renamed or removed one is a compile error at the stale call site
|
|
612
|
+
* instead of a silent `undefined` at runtime. The ref is still `| null`
|
|
613
|
+
* and the calls keep their `?.` because the canvas mounts lazily — the
|
|
614
|
+
* optionality lives at the binding, not in the type.
|
|
615
|
+
*/
|
|
616
|
+
let canvasRef: WithoutIndexKeys<InstanceType<typeof import('@widgetic/canvas').Canvas>> | null = null;
|
|
617
|
+
/**
|
|
618
|
+
* HTML iframe overlays over Fabric WidgetShapes (W3).
|
|
619
|
+
*
|
|
620
|
+
* Typed as the component's **own instance type**, not a hand-written
|
|
621
|
+
* mirror: a Svelte 5 component is a call-signature type whose return is
|
|
622
|
+
* the exported instance surface, so `ReturnType` of the imported component
|
|
623
|
+
* is exactly what `bind:this` delivers. The previous literal shape
|
|
624
|
+
* (`sync`/`requestReload`/`cancelReloads`/`setSelectedVersion`, all
|
|
625
|
+
* optional) had to be edited in lockstep with the component's `export
|
|
626
|
+
* function` block — a new export left every call site type-unchecked
|
|
627
|
+
* against it, a signature change drifted silently. Derived, both
|
|
628
|
+
* directions are compile errors at the stale site instead. The ref is
|
|
629
|
+
* still `| null` and the calls keep their `?.` because the canvas mounts
|
|
630
|
+
* lazily — the optionality lives at the binding, not in the type.
|
|
631
|
+
*/
|
|
632
|
+
let widgetShapeLivePreviewsRef: ReturnType<typeof WidgetShapeLivePreviews> | null = null;
|
|
514
633
|
/** Selected WidgetShape id for chrome highlight (from Fabric selection). */
|
|
515
634
|
let canvasSelectedWidgetShapeId: string | null = null;
|
|
516
|
-
let widgetDetailsModulePromise: Promise<
|
|
635
|
+
let widgetDetailsModulePromise: Promise<typeof WidgetDetailsComponent> | null = null;
|
|
517
636
|
|
|
518
|
-
function loadWidgetDetailsModule(): Promise<
|
|
637
|
+
function loadWidgetDetailsModule(): Promise<typeof WidgetDetailsComponent> {
|
|
519
638
|
if (!widgetDetailsModulePromise) {
|
|
520
639
|
widgetDetailsModulePromise = import('./components/WidgetDetails.svelte').then((wdModule) => {
|
|
521
640
|
WidgetDetails = wdModule.default;
|
|
@@ -891,6 +1010,16 @@
|
|
|
891
1010
|
let currentGenerationOperationId: string | null = null;
|
|
892
1011
|
let generateCodeStatus: string | null = null;
|
|
893
1012
|
let lastGenerationFailed = false; // Track if last generation failed for retry UI
|
|
1013
|
+
let publishPlanMessageByWidgetId: Record<string, string> = {};
|
|
1014
|
+
|
|
1015
|
+
function isPlanLimitMessage(message: string | null | undefined): boolean {
|
|
1016
|
+
if (!message) return false;
|
|
1017
|
+
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);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function openUpgradePlans() {
|
|
1021
|
+
window.location.assign('/workspaces/personal/settings/billing');
|
|
1022
|
+
}
|
|
894
1023
|
let lastGenerationError: string | null = null; // Store the error message for display
|
|
895
1024
|
let lastGenerationPrompt: string | null = null; // Last prompt used, for retrying failed generations
|
|
896
1025
|
let pendingAutoGenerateWidgetId: string | null = null; // Widget created from canvas that needs auto code generation
|
|
@@ -922,6 +1051,7 @@
|
|
|
922
1051
|
let chatMethods: {
|
|
923
1052
|
sendMessage: (content: string, attachments?: File[]) => Promise<void>;
|
|
924
1053
|
addAssistantMessage: (content: string, skipPersist?: boolean, suggestions?: ClarificationSuggestion[]) => void;
|
|
1054
|
+
addDesignControlsMessage?: (content: string, designControls: DesignControlRequest) => void;
|
|
925
1055
|
addUserMessage: (content: string, attachments?: Array<{ id: string; url: string; file_type: string; file_name: string }>) => void;
|
|
926
1056
|
getConversationId: () => string | null;
|
|
927
1057
|
addImageAttachment?: (dataUrl: string, fileName?: string) => void;
|
|
@@ -960,6 +1090,45 @@
|
|
|
960
1090
|
// Populated via Chat's onConversationChange callback and passed to generateCode()
|
|
961
1091
|
// so the backend can load multi-turn LLM history (Aider-style incremental edits).
|
|
962
1092
|
let currentChatConversationId: string | null = null;
|
|
1093
|
+
let brandKitEnabled = false;
|
|
1094
|
+
let widgetLanguage = 'svelte';
|
|
1095
|
+
let librariesEnabled = false;
|
|
1096
|
+
let brandPrimary = '#314F2F';
|
|
1097
|
+
let brandSecondary = '#7BA668';
|
|
1098
|
+
let brandTitle = '#0F2010';
|
|
1099
|
+
let brandSub = '#5C6B58';
|
|
1100
|
+
let brandFonts = ['Inter'];
|
|
1101
|
+
let brandKitLibraries: string[] = [];
|
|
1102
|
+
let libraryGitUrl = '';
|
|
1103
|
+
let libraryFileName = '';
|
|
1104
|
+
let libraryUploads: { name: string; content: string; encoding: string }[] = [];
|
|
1105
|
+
let fontUploads: { name: string; content: string; encoding: string }[] = [];
|
|
1106
|
+
|
|
1107
|
+
function brandKitForRequest() {
|
|
1108
|
+
const kit: {
|
|
1109
|
+
colors?: { primary: string; secondary: string; title: string; sub: string };
|
|
1110
|
+
fonts?: string[];
|
|
1111
|
+
libraries?: string[];
|
|
1112
|
+
uploads?: { name: string; content: string; encoding: string }[];
|
|
1113
|
+
fontFiles?: { name: string; content: string; encoding: string }[];
|
|
1114
|
+
} = {};
|
|
1115
|
+
if (brandKitEnabled) {
|
|
1116
|
+
kit.colors = {
|
|
1117
|
+
primary: brandPrimary,
|
|
1118
|
+
secondary: brandSecondary,
|
|
1119
|
+
title: brandTitle,
|
|
1120
|
+
sub: brandSub,
|
|
1121
|
+
};
|
|
1122
|
+
const families = brandFonts.map((family) => family.trim()).filter(Boolean).slice(0, 3);
|
|
1123
|
+
if (families.length > 0) kit.fonts = families;
|
|
1124
|
+
}
|
|
1125
|
+
// Preset libraries are a debug test. An empty list is an explicit ban.
|
|
1126
|
+
if (librariesEnabled && debugMode) kit.libraries = [...brandKitLibraries];
|
|
1127
|
+
if (brandKitEnabled && fontUploads.length > 0) kit.fontFiles = fontUploads.map((file) => ({ ...file }));
|
|
1128
|
+
if (libraryUploads.length > 0) kit.uploads = libraryUploads.map((file) => ({ ...file }));
|
|
1129
|
+
if (!kit.colors && !kit.fonts && !kit.libraries && !kit.uploads && !kit.fontFiles) return undefined;
|
|
1130
|
+
return kit;
|
|
1131
|
+
}
|
|
963
1132
|
|
|
964
1133
|
/** Prompt mentions a reference image but chat has no attachment (common UX mistake). */
|
|
965
1134
|
function promptImpliesVisionReference(prompt: string): boolean {
|
|
@@ -1133,6 +1302,40 @@
|
|
|
1133
1302
|
}
|
|
1134
1303
|
});
|
|
1135
1304
|
|
|
1305
|
+
// Shadow-mode measurement: bucket every send by how it could be resolved
|
|
1306
|
+
// (literal/arithmetic, bounded-set semantic, free text, new property, or
|
|
1307
|
+
// not a design ask). Records + logs only — it never changes behavior.
|
|
1308
|
+
// The bucket also decides whether the Jev fallback is worth a dial.
|
|
1309
|
+
// Inspect from the console: `__widgeticDesignIntentTelemetry.report()`.
|
|
1310
|
+
// The whole classification is kept, not just the bucket: the Jev dial below is
|
|
1311
|
+
// attributed to the *ask shape* inside that bucket (a property was named, or
|
|
1312
|
+
// only a value was), which is what makes its fall-through reasons actionable.
|
|
1313
|
+
let designIntentClassification: DesignIntentClassification | null = null;
|
|
1314
|
+
try {
|
|
1315
|
+
designIntentClassification = recordDesignIntentTelemetry(
|
|
1316
|
+
content,
|
|
1317
|
+
getPanelDisplayState(codegenWidgetId).compositionDesignSchema
|
|
1318
|
+
);
|
|
1319
|
+
} catch {
|
|
1320
|
+
/* telemetry must never break a send */
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Deterministic design-tweak fast path: explicit values and arithmetic
|
|
1324
|
+
// resolve against the loaded design schema in code, so an unambiguous
|
|
1325
|
+
// property change never pays for a 30–300s codegen turn.
|
|
1326
|
+
if (!attachments?.length && !sendOptions?.skipIntentGate) {
|
|
1327
|
+
if (tryApplyDesignIntent(codegenWidgetId, content)) return;
|
|
1328
|
+
// Jev takes over ONLY the bounded-set-semantic bucket — the residual
|
|
1329
|
+
// where the property or value needs judgment but the value space is
|
|
1330
|
+
// closed. Free text and new properties can only fall through (Jev cannot
|
|
1331
|
+
// generate) and a non-design ask is not ours at all, so none of them is
|
|
1332
|
+
// ever dialed.
|
|
1333
|
+
const dialJev = isJevCandidateBucket(designIntentClassification?.bucket);
|
|
1334
|
+
if (dialJev && (await tryApplyDesignIntentViaJev(codegenWidgetId, content, designIntentClassification))) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1136
1339
|
const effectivePromptImages =
|
|
1137
1340
|
panelPromptImages.length > 0
|
|
1138
1341
|
? panelPromptImages
|
|
@@ -2116,6 +2319,12 @@
|
|
|
2116
2319
|
// Suggestion chips from a persisted clarification card — restored
|
|
2117
2320
|
// on reload exactly as they were served.
|
|
2118
2321
|
suggestions: Array.isArray(m.suggestions) && m.suggestions.length > 0 ? m.suggestions : undefined,
|
|
2322
|
+
// The inline design-control card this turn carried, restored on
|
|
2323
|
+
// reload so the reply keeps its controls. The server has already
|
|
2324
|
+
// normalized the payload to a renderable shape (see the messages
|
|
2325
|
+
// service); `compositionId` on it decides whether the card can still
|
|
2326
|
+
// read this composition's live values.
|
|
2327
|
+
designControls: m.design_controls ?? m.designControls ?? undefined,
|
|
2119
2328
|
isCommit: m.is_commit || m.isCommit || false,
|
|
2120
2329
|
// What the turn shipped, in the commit's own words — persisted beside
|
|
2121
2330
|
// is_commit by the codegen pipeline and rendered on the turn's commit
|
|
@@ -2295,6 +2504,10 @@
|
|
|
2295
2504
|
}>,
|
|
2296
2505
|
suggestions?: ClarificationSuggestion[],
|
|
2297
2506
|
persistWidgetId?: string | null,
|
|
2507
|
+
/** The inline design-control card this reply carries, when a directed
|
|
2508
|
+
* design tweak was applied with no codegen. Persisted with the reply so
|
|
2509
|
+
* the card survives a reload and re-attaches to its composition. */
|
|
2510
|
+
designControls?: DesignControlRequest | null,
|
|
2298
2511
|
): Promise<any> {
|
|
2299
2512
|
try {
|
|
2300
2513
|
// Draft convert panels hold a client-only conversation until the widget
|
|
@@ -2332,6 +2545,9 @@
|
|
|
2332
2545
|
// Clarification-card suggestions ride the same write as the reply
|
|
2333
2546
|
// they belong to, so a reload re-renders the card with its chips.
|
|
2334
2547
|
...(suggestions && suggestions.length > 0 ? { suggestions } : {}),
|
|
2548
|
+
// Same for the inline design-control card: one write, so a reload
|
|
2549
|
+
// re-renders the reply and its controls — not half a turn.
|
|
2550
|
+
...(designControls ? { design_controls: designControls } : {}),
|
|
2335
2551
|
};
|
|
2336
2552
|
const persistMessage = async (payload: typeof createPayload) => {
|
|
2337
2553
|
const response = await messagesClient.createMessageRaw({
|
|
@@ -2549,6 +2765,10 @@
|
|
|
2549
2765
|
// API returns camelCase (via responseTransformMiddleware), fallback to snake_case
|
|
2550
2766
|
commitSha?: string | null;
|
|
2551
2767
|
commit_sha?: string | null;
|
|
2768
|
+
/** What the change shipped, in the pipeline's own words — the same title
|
|
2769
|
+
* the chat turn's commit chrome shows. Null on pre-title versions. */
|
|
2770
|
+
commitTitle?: string | null;
|
|
2771
|
+
commit_title?: string | null;
|
|
2552
2772
|
artifactUrl?: string;
|
|
2553
2773
|
artifact_url?: string;
|
|
2554
2774
|
/**
|
|
@@ -2573,6 +2793,8 @@
|
|
|
2573
2793
|
let isHistoryOpen = false;
|
|
2574
2794
|
let isLoadingHistory = false;
|
|
2575
2795
|
let isSettingLiveVersion: number | null = null; // track which version is being set live
|
|
2796
|
+
/** The version whose title rename is in flight, as `widgetId:version`. */
|
|
2797
|
+
let renamingTitleFor: string | null = null;
|
|
2576
2798
|
let liveHistoryPanelTop = 0;
|
|
2577
2799
|
let liveHistoryPanelRight = 0;
|
|
2578
2800
|
/** True once publish history fetch completed for the current widget (or widget was never published). */
|
|
@@ -2677,8 +2899,8 @@
|
|
|
2677
2899
|
// COMPOSITION EDITOR STATE
|
|
2678
2900
|
// =============================================================================
|
|
2679
2901
|
let compositionEditorOpen = false;
|
|
2680
|
-
let compositionDesignSchema:
|
|
2681
|
-
let compositionContentSchema:
|
|
2902
|
+
let compositionDesignSchema: DesignSchemaView | null = null;
|
|
2903
|
+
let compositionContentSchema: ContentSchemaView | null = null;
|
|
2682
2904
|
let compositionSchemasLoading = false;
|
|
2683
2905
|
let compositionSchemasError: string | null = null;
|
|
2684
2906
|
|
|
@@ -2712,6 +2934,9 @@
|
|
|
2712
2934
|
}
|
|
2713
2935
|
|
|
2714
2936
|
function handleChatMessagesResize(widgetId: string, deltaPx: number): void {
|
|
2937
|
+
// Sub-pixel footer jitter was rewriting the chat height every frame and
|
|
2938
|
+
// re-rendering the whole creator while code generation streamed.
|
|
2939
|
+
if (!Number.isFinite(deltaPx) || Math.abs(deltaPx) < 2) return;
|
|
2715
2940
|
const measured = document
|
|
2716
2941
|
.querySelector(`[data-widget-chat-height="${widgetId}"]`)
|
|
2717
2942
|
?.getBoundingClientRect().height;
|
|
@@ -2719,8 +2944,8 @@
|
|
|
2719
2944
|
measured && measured > 0
|
|
2720
2945
|
? measured
|
|
2721
2946
|
: (getChatContainerHeightForPanel(widgetId) ?? CHAT_DEFAULT_HEIGHT);
|
|
2722
|
-
const next = Math.max(CHAT_MIN_HEIGHT, Math.min(CHAT_MAX_HEIGHT, current + deltaPx));
|
|
2723
|
-
|
|
2947
|
+
const next = Math.max(CHAT_MIN_HEIGHT, Math.min(CHAT_MAX_HEIGHT, Math.round(current + deltaPx)));
|
|
2948
|
+
if (Math.abs(next - current) < 2) return;
|
|
2724
2949
|
patchPanelSnapshot(widgetId, { chatContainerHeight: next });
|
|
2725
2950
|
}
|
|
2726
2951
|
|
|
@@ -2763,19 +2988,77 @@
|
|
|
2763
2988
|
});
|
|
2764
2989
|
}
|
|
2765
2990
|
|
|
2766
|
-
|
|
2991
|
+
/** A component property as the design schema carries it. Field set is open —
|
|
2992
|
+
* consumers read known keys and every schema shape is producer-defined — but
|
|
2993
|
+
* the keys the editor itself reads are named. */
|
|
2994
|
+
interface DesignSchemaPropertyView {
|
|
2995
|
+
id?: string;
|
|
2996
|
+
name?: string;
|
|
2997
|
+
description?: string;
|
|
2998
|
+
inputController?: { type?: string } & Record<string, unknown>;
|
|
2999
|
+
[key: string]: unknown;
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
/** One component group of the design schema (the `componentGroups` array's
|
|
3003
|
+
* members). Open to extra keys; the editor reads `name` and `components`. */
|
|
3004
|
+
interface DesignSchemaGroupView {
|
|
3005
|
+
name?: string;
|
|
3006
|
+
components?: Array<{
|
|
3007
|
+
properties?: DesignSchemaPropertyView[];
|
|
3008
|
+
[key: string]: unknown;
|
|
3009
|
+
}>;
|
|
3010
|
+
[key: string]: unknown;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
/**
|
|
3014
|
+
* The design schema as this file's pipeline actually carries it: after
|
|
3015
|
+
* `normalizeDesignSchemaFromApiResponse` it is **either a group array (the
|
|
3016
|
+
* merged single-group form) or null** — the envelope variants
|
|
3017
|
+
* (`componentGroups` on an object, legacy `theme`-only payloads) are
|
|
3018
|
+
* collapsed upstream of this type. The API's generated model
|
|
3019
|
+
* (`GetCompositionDesignSchema200Response`) types `componentGroups` only as
|
|
3020
|
+
* `Array<object>`, so these views encode the shape the normalizer
|
|
3021
|
+
* guarantees, not the shape the OpenAPI spec guesses.
|
|
3022
|
+
*/
|
|
3023
|
+
type DesignSchemaView = DesignSchemaGroupView[];
|
|
3024
|
+
|
|
3025
|
+
/** One content-item entry of the content schema (the `contentItems` array's
|
|
3026
|
+
* members). `id` and `properties` are what the merge/preview helpers read. */
|
|
3027
|
+
interface ContentSchemaItemView {
|
|
3028
|
+
id?: string;
|
|
3029
|
+
properties?: unknown[];
|
|
3030
|
+
[key: string]: unknown;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* The content schema as this file's pipeline carries it: the raw 200
|
|
3035
|
+
* payload's object form (may arrive wrapped as `{ data: … }` from the
|
|
3036
|
+
* response envelope), with the two keys the editor reads named —
|
|
3037
|
+
* `contentItems` (array form; the legacy object form is tolerated at the
|
|
3038
|
+
* read sites and Array-guarded before use) plus the open remainder.
|
|
3039
|
+
*/
|
|
3040
|
+
interface ContentSchemaView {
|
|
3041
|
+
/** Response-envelope artifact of the upstream API; tolerated, never authored here. */
|
|
3042
|
+
data?: { contentItems?: ContentSchemaItemView[] | Record<string, unknown> };
|
|
3043
|
+
contentItems?: ContentSchemaItemView[] | Record<string, unknown>;
|
|
3044
|
+
[key: string]: unknown;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function normalizeDesignSchemaFromApiResponse(designRes: unknown): DesignSchemaView | null {
|
|
2767
3048
|
if (!designRes) return null;
|
|
2768
3049
|
const payload = (designRes as { data?: unknown })?.data ?? designRes;
|
|
2769
3050
|
if (!payload || typeof payload !== 'object') return null;
|
|
2770
|
-
if (Array.isArray(payload)) return payload;
|
|
3051
|
+
if (Array.isArray(payload)) return payload as DesignSchemaView;
|
|
2771
3052
|
const record = payload as Record<string, unknown>;
|
|
2772
3053
|
const groups = record.componentGroups ?? record.component_groups;
|
|
2773
3054
|
if (Array.isArray(groups)) {
|
|
2774
3055
|
if (groups.length > 1) {
|
|
2775
|
-
|
|
3056
|
+
// The view-shape coercion happens here, at the normalizer — the one
|
|
3057
|
+
// place raw API JSON is pronounced to be the editor's schema view.
|
|
3058
|
+
const mergedComponents = groups.flatMap((g: { components?: DesignSchemaPropertyView[] }) => g.components || []);
|
|
2776
3059
|
return [{ name: (groups[0] as { name?: string })?.name || 'Design', components: mergedComponents }];
|
|
2777
3060
|
}
|
|
2778
|
-
return groups;
|
|
3061
|
+
return groups as DesignSchemaView;
|
|
2779
3062
|
}
|
|
2780
3063
|
// Legacy VM default schema: { theme: { colors: ... } } — not editable via PropsEditor
|
|
2781
3064
|
if (record.theme && !groups) return null;
|
|
@@ -2826,10 +3109,10 @@
|
|
|
2826
3109
|
}
|
|
2827
3110
|
}
|
|
2828
3111
|
|
|
2829
|
-
function normalizeContentSchemaFromApiResponse(contentRes: unknown):
|
|
3112
|
+
function normalizeContentSchemaFromApiResponse(contentRes: unknown): ContentSchemaView | null {
|
|
2830
3113
|
if (!contentRes) return null;
|
|
2831
3114
|
const payload = (contentRes as { data?: unknown })?.data ?? contentRes;
|
|
2832
|
-
return payload && typeof payload === 'object' ? payload : null;
|
|
3115
|
+
return payload && typeof payload === 'object' ? (payload as ContentSchemaView) : null;
|
|
2833
3116
|
}
|
|
2834
3117
|
|
|
2835
3118
|
const SCHEMA_LOAD_TIMEOUT_MS = 20000;
|
|
@@ -2839,8 +3122,8 @@
|
|
|
2839
3122
|
const schemaLoadWaitQueue: string[] = [];
|
|
2840
3123
|
/** Per-widget schema cache — survives focus switches and snapshot merges. */
|
|
2841
3124
|
const widgetSchemaCache = new Map<string, {
|
|
2842
|
-
design:
|
|
2843
|
-
content:
|
|
3125
|
+
design: DesignSchemaView | null;
|
|
3126
|
+
content: ContentSchemaView | null;
|
|
2844
3127
|
error: string | null;
|
|
2845
3128
|
}>();
|
|
2846
3129
|
|
|
@@ -3065,8 +3348,8 @@
|
|
|
3065
3348
|
compositionSchemasError = null;
|
|
3066
3349
|
}
|
|
3067
3350
|
|
|
3068
|
-
let designSchema:
|
|
3069
|
-
let contentSchema:
|
|
3351
|
+
let designSchema: DesignSchemaView | null = null;
|
|
3352
|
+
let contentSchema: ContentSchemaView | null = null;
|
|
3070
3353
|
let schemaError: string | null = null;
|
|
3071
3354
|
|
|
3072
3355
|
try {
|
|
@@ -3089,7 +3372,7 @@
|
|
|
3089
3372
|
|
|
3090
3373
|
designSchema = ensureSubtitleFontInDesignSchema(
|
|
3091
3374
|
normalizeDesignSchemaFromApiResponse(designRes)
|
|
3092
|
-
);
|
|
3375
|
+
) as DesignSchemaView | null;
|
|
3093
3376
|
contentSchema = normalizeContentSchemaFromApiResponse(contentRes);
|
|
3094
3377
|
|
|
3095
3378
|
if (!designSchema && !contentSchema) {
|
|
@@ -3452,6 +3735,206 @@
|
|
|
3452
3735
|
return out;
|
|
3453
3736
|
}
|
|
3454
3737
|
|
|
3738
|
+
/**
|
|
3739
|
+
* Deterministic design-tweak fast path for the canvas chat.
|
|
3740
|
+
*
|
|
3741
|
+
* `off` — never resolve; every message goes to codegen (shipped behavior).
|
|
3742
|
+
* `shadow` — resolve + log, but still send to codegen (measure the flip rate).
|
|
3743
|
+
* `enforce` — apply the resolved change to the live preview, persist it like
|
|
3744
|
+
* any other design edit, render the inline controller in chat, and
|
|
3745
|
+
* skip codegen entirely — no LLM call.
|
|
3746
|
+
*/
|
|
3747
|
+
const DESIGN_INTENT_RESOLVER = (
|
|
3748
|
+
(import.meta.env as unknown as Record<string, string | undefined>).VITE_DESIGN_INTENT_RESOLVER ??
|
|
3749
|
+
'enforce'
|
|
3750
|
+
) as 'off' | 'shadow' | 'enforce';
|
|
3751
|
+
|
|
3752
|
+
/** Apply design values to the live preview + persist, mirroring the editor path. */
|
|
3753
|
+
function applyDesignValuesToPreview(widgetId: string, changes: Record<string, unknown>): void {
|
|
3754
|
+
const ps = getPanelDisplayState(widgetId);
|
|
3755
|
+
const base = ps.liveDesignValues ?? {};
|
|
3756
|
+
const next = { ...base, ...changes };
|
|
3757
|
+
if (widgetId === focusedPanelWidgetId) {
|
|
3758
|
+
liveDesignValues = next;
|
|
3759
|
+
} else {
|
|
3760
|
+
patchPanelSnapshot(widgetId, { liveDesignValues: next });
|
|
3761
|
+
}
|
|
3762
|
+
getWidgetDetailsRef(widgetId)?.sendMessageToPreview({
|
|
3763
|
+
type: 'widgetic:update',
|
|
3764
|
+
design: flattenDesignValuesForPreview(next)
|
|
3765
|
+
});
|
|
3766
|
+
compositionDirty = true;
|
|
3767
|
+
if (widgetId === focusedPanelWidgetId) {
|
|
3768
|
+
syncFocusedPanelEditorSnapshot();
|
|
3769
|
+
// Design edits are committed as soon as they are made, so the next codegen
|
|
3770
|
+
// turn already sees the updated values.
|
|
3771
|
+
if (selectedComposition && selectedWidgetId === widgetId) {
|
|
3772
|
+
void updateCompositionValues();
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
/**
|
|
3778
|
+
* Property ids present in a design schema, memoized by schema identity.
|
|
3779
|
+
*
|
|
3780
|
+
* The chat design card asks whether the property it controls still exists so
|
|
3781
|
+
* it can disable a control whose property a regeneration dropped instead of
|
|
3782
|
+
* writing a dead value. That question is asked for every open panel on every
|
|
3783
|
+
* display revision (i.e. on every slider tick), so re-walking the schema each
|
|
3784
|
+
* time is wasteful. `undefined` means there is no schema to judge by yet —
|
|
3785
|
+
* the card then leaves its controls live rather than disabling them.
|
|
3786
|
+
*/
|
|
3787
|
+
let designPropertyIdsCache: { schema: unknown; ids: Set<string> } | null = null;
|
|
3788
|
+
function designPropertyIdsFor(schema: unknown): Set<string> | undefined {
|
|
3789
|
+
if (!schema) return undefined;
|
|
3790
|
+
if (designPropertyIdsCache?.schema === schema) return designPropertyIdsCache.ids;
|
|
3791
|
+
const ids = new Set(collectDesignProperties(schema).map((property) => property.id));
|
|
3792
|
+
designPropertyIdsCache = { schema, ids };
|
|
3793
|
+
return ids;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
/**
|
|
3797
|
+
* Try to satisfy a chat message as a deterministic design tweak (no LLM).
|
|
3798
|
+
* Returns true when the message was handled and codegen must be skipped.
|
|
3799
|
+
*/
|
|
3800
|
+
function tryApplyDesignIntent(widgetId: string, message: string): boolean {
|
|
3801
|
+
if (DESIGN_INTENT_RESOLVER === 'off') return false;
|
|
3802
|
+
const ps = getPanelDisplayState(widgetId);
|
|
3803
|
+
const schema = ps.compositionDesignSchema;
|
|
3804
|
+
if (!schema) return false;
|
|
3805
|
+
|
|
3806
|
+
const liveValues = ps.liveDesignValues ?? {};
|
|
3807
|
+
const resolution = resolveDesignIntent(message, schema, liveValues);
|
|
3808
|
+
if (!resolution) return false;
|
|
3809
|
+
|
|
3810
|
+
if (DESIGN_INTENT_RESOLVER === 'shadow') {
|
|
3811
|
+
console.log('[DesignIntent] shadow', resolution);
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
const changes: Record<string, unknown> = {};
|
|
3816
|
+
for (const change of resolution.changes) changes[change.propertyId] = change.value;
|
|
3817
|
+
applyDesignValuesToPreview(widgetId, changes);
|
|
3818
|
+
// The card records the composition the change landed in, so a reload can
|
|
3819
|
+
// re-attach it to that composition's live values instead of whatever
|
|
3820
|
+
// composition happens to be selected when the conversation is reopened.
|
|
3821
|
+
const card = toDesignControlRequest(
|
|
3822
|
+
resolution,
|
|
3823
|
+
schema,
|
|
3824
|
+
{ ...liveValues, ...changes },
|
|
3825
|
+
ps.selectedComposition?.id ?? null
|
|
3826
|
+
);
|
|
3827
|
+
getChatMethodsForWidget(widgetId)?.addDesignControlsMessage?.(resolution.summary, card);
|
|
3828
|
+
return true;
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
/**
|
|
3832
|
+
* Jev-backed fallback for the deterministic resolver: when the message names
|
|
3833
|
+
* a design property semantically and no literal value can be parsed, ask the
|
|
3834
|
+
* gateway to classify the property and a bounded value. Acts only when the
|
|
3835
|
+
* gateway says `acted` (enforce mode + confident verdict); otherwise the
|
|
3836
|
+
* caller generates exactly as before. Never throws, never blocks a send.
|
|
3837
|
+
*
|
|
3838
|
+
* The send's own classification rides along so the shadow telemetry can split
|
|
3839
|
+
* every dial's outcome by the ask shape that produced it — a fall-through where
|
|
3840
|
+
* the property was named indicts the value option strings, one where it wasn't
|
|
3841
|
+
* indicts the property strings or the confidence floor.
|
|
3842
|
+
*/
|
|
3843
|
+
async function tryApplyDesignIntentViaJev(
|
|
3844
|
+
widgetId: string,
|
|
3845
|
+
message: string,
|
|
3846
|
+
classification: DesignIntentClassification | null
|
|
3847
|
+
): Promise<boolean> {
|
|
3848
|
+
const ps = getPanelDisplayState(widgetId);
|
|
3849
|
+
const schema = ps.compositionDesignSchema;
|
|
3850
|
+
if (!schema || !apiBasePath) return false;
|
|
3851
|
+
|
|
3852
|
+
const result = await resolveDesignIntentViaJev({
|
|
3853
|
+
apiBasePath,
|
|
3854
|
+
token: authenticatedClientsToken,
|
|
3855
|
+
message,
|
|
3856
|
+
designSchema: schema,
|
|
3857
|
+
currentValues: ps.liveDesignValues ?? {}
|
|
3858
|
+
});
|
|
3859
|
+
if (!result) {
|
|
3860
|
+
recordDesignIntentJevOutcome({
|
|
3861
|
+
source: 'client_unavailable',
|
|
3862
|
+
acted: false,
|
|
3863
|
+
reason: 'no_response',
|
|
3864
|
+
classification
|
|
3865
|
+
});
|
|
3866
|
+
return false;
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
recordDesignIntentJevOutcome({
|
|
3870
|
+
source: result.source,
|
|
3871
|
+
acted: result.acted,
|
|
3872
|
+
reason: result.reason,
|
|
3873
|
+
classification
|
|
3874
|
+
});
|
|
3875
|
+
|
|
3876
|
+
if (!result.acted || !result.property) {
|
|
3877
|
+
if (debugMode || result.source === 'jev') {
|
|
3878
|
+
console.log('[DesignIntentJev] fall through:', result.reason, result.property ?? '');
|
|
3879
|
+
}
|
|
3880
|
+
return false;
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
const property = collectDesignProperties(schema).find((p) => p.id === result.property);
|
|
3884
|
+
const label = property?.label ?? result.property;
|
|
3885
|
+
applyDesignValuesToPreview(widgetId, { [result.property]: result.value });
|
|
3886
|
+
const card: DesignControlRequest = {
|
|
3887
|
+
summary: `Set ${label} to ${String(result.value)}.`,
|
|
3888
|
+
properties: [
|
|
3889
|
+
{
|
|
3890
|
+
id: result.property,
|
|
3891
|
+
label,
|
|
3892
|
+
controllerType: property?.controllerType ?? '',
|
|
3893
|
+
value: result.value,
|
|
3894
|
+
options: property?.options,
|
|
3895
|
+
min: property?.min,
|
|
3896
|
+
max: property?.max,
|
|
3897
|
+
step: property?.step,
|
|
3898
|
+
helpText: property?.helpText
|
|
3899
|
+
}
|
|
3900
|
+
],
|
|
3901
|
+
// Same attribution as the deterministic path: the card is persisted
|
|
3902
|
+
// with the composition it was authored against (see `toDesignControlRequest`).
|
|
3903
|
+
compositionId: ps.selectedComposition?.id ?? null
|
|
3904
|
+
};
|
|
3905
|
+
getChatMethodsForWidget(widgetId)?.addDesignControlsMessage?.(card.summary, card);
|
|
3906
|
+
return true;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
/**
|
|
3910
|
+
* Classify a hand-drawn canvas stroke the Canvas's own geometry engine could
|
|
3911
|
+
* not decide.
|
|
3912
|
+
*
|
|
3913
|
+
* The Canvas measures the stroke and gates out every shape the measurements
|
|
3914
|
+
* rule out, then asks here only when several shapes remain. The gateway owns
|
|
3915
|
+
* the JEV decision and its confidence gate; this returns its shape, or null
|
|
3916
|
+
* (leave the stroke exactly as drawn) when it declines, degrades, or is not
|
|
3917
|
+
* configured. Null is always a valid answer, so nothing here can block a draw.
|
|
3918
|
+
*/
|
|
3919
|
+
async function handleRecognizeStroke(
|
|
3920
|
+
request: StrokeRecognitionRequest
|
|
3921
|
+
): Promise<StrokeRecognitionResult | null> {
|
|
3922
|
+
const reply = await recognizeStrokeViaJev({
|
|
3923
|
+
apiBasePath,
|
|
3924
|
+
token: authenticatedClientsToken,
|
|
3925
|
+
request
|
|
3926
|
+
});
|
|
3927
|
+
if (debugMode) {
|
|
3928
|
+
console.log(
|
|
3929
|
+
'[CanvasShapeSnap]',
|
|
3930
|
+
`candidates: ${request.candidates.map((candidate) => candidate.shape).join(', ')}`,
|
|
3931
|
+
`→ ${reply?.shape ?? 'as drawn'}`,
|
|
3932
|
+
`(${reply?.source ?? 'client_unavailable'} / ${reply?.reason ?? 'no_response'})`
|
|
3933
|
+
);
|
|
3934
|
+
}
|
|
3935
|
+
return reply;
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3455
3938
|
/** Handle property change from PropsEditor — send to widget preview iframe */
|
|
3456
3939
|
function handleEditorPropertyChange(event: CustomEvent<{ schema: string; propertyId: string; value: unknown; opacity?: number; itemId?: string }>) {
|
|
3457
3940
|
const { schema, propertyId, value, itemId } = event.detail;
|
|
@@ -3496,10 +3979,12 @@
|
|
|
3496
3979
|
/** Handle content items reorder/add/delete from PropsEditor */
|
|
3497
3980
|
function handleEditorContentItemsChanged(event: CustomEvent<{ items: unknown[] }>) {
|
|
3498
3981
|
const incoming = Array.isArray(event.detail.items) ? event.detail.items : [];
|
|
3499
|
-
|
|
3500
|
-
?? compositionContentSchema?.data?.contentItems
|
|
3501
|
-
|
|
3502
|
-
|
|
3982
|
+
const schemaItems = compositionContentSchema?.contentItems
|
|
3983
|
+
?? compositionContentSchema?.data?.contentItems;
|
|
3984
|
+
liveContentItems = mergeContentItemsWithSchema(
|
|
3985
|
+
incoming,
|
|
3986
|
+
Array.isArray(schemaItems) ? schemaItems : []
|
|
3987
|
+
);
|
|
3503
3988
|
// console.log('[PropsEditor→Preview] Content items changed:', liveContentItems.length, 'items');
|
|
3504
3989
|
getWidgetDetailsRef(focusedPanelWidgetId)?.sendMessageToPreview({
|
|
3505
3990
|
type: 'widgetic:update',
|
|
@@ -4711,8 +5196,7 @@
|
|
|
4711
5196
|
return !cid || cid === targetCanvasId;
|
|
4712
5197
|
}) || fromList[0] || null;
|
|
4713
5198
|
if (primaryId) {
|
|
4714
|
-
|
|
4715
|
-
await persistPrimaryWidgetId(primaryId);
|
|
5199
|
+
console.warn('[CreatorApp] Canvas has no saved primary — showing', primaryId.slice(0, 8), 'without replacing the saved one');
|
|
4716
5200
|
}
|
|
4717
5201
|
}
|
|
4718
5202
|
if (primaryId) {
|
|
@@ -8618,11 +9102,22 @@ Requirements:
|
|
|
8618
9102
|
promptImages = imagesToSend;
|
|
8619
9103
|
savePromptImagesToSession();
|
|
8620
9104
|
}
|
|
8621
|
-
} else if (promptImpliesVisionReference(prompt)) {
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
9105
|
+
} else if (promptImpliesVisionReference(prompt) && !options.forkUserMessage) {
|
|
9106
|
+
const shapeDataUrl =
|
|
9107
|
+
typeof canvasRef?.getWidgetShapeDataUrl === 'function'
|
|
9108
|
+
? canvasRef.getWidgetShapeDataUrl(generationWidgetId)
|
|
9109
|
+
: null;
|
|
9110
|
+
if (typeof shapeDataUrl === 'string' && shapeDataUrl.startsWith('data:image/') && shapeDataUrl.length > 100) {
|
|
9111
|
+
const compressedShape = await compressImageDataUrlForCodegen(shapeDataUrl);
|
|
9112
|
+
if (compressedShape.startsWith('data:image/') && compressedShape.length > 100) {
|
|
9113
|
+
imagesToSend = [compressedShape];
|
|
9114
|
+
}
|
|
9115
|
+
} else {
|
|
9116
|
+
console.warn(
|
|
9117
|
+
'[WidgetCreator] Codegen proceeding WITHOUT image although prompt references one — widget',
|
|
9118
|
+
generationWidgetId,
|
|
9119
|
+
);
|
|
9120
|
+
}
|
|
8626
9121
|
}
|
|
8627
9122
|
let coderModelForRequest = selectedModelId || defaultModelId;
|
|
8628
9123
|
if (imagesToSend?.length || attachmentIdsToSend?.length) {
|
|
@@ -8649,7 +9144,7 @@ Requirements:
|
|
|
8649
9144
|
duration: 8000
|
|
8650
9145
|
});
|
|
8651
9146
|
}
|
|
8652
|
-
} else if (promptImpliesVisionReference(prompt)) {
|
|
9147
|
+
} else if (promptImpliesVisionReference(prompt) && !options.forkUserMessage) {
|
|
8653
9148
|
showToast('warning', 'Generare fără imagine', {
|
|
8654
9149
|
description:
|
|
8655
9150
|
'Request-ul merge fără vision (DeepSeek etc.). Atașează PNG/JPEG în chat înainte de Send.',
|
|
@@ -8681,7 +9176,9 @@ Requirements:
|
|
|
8681
9176
|
? undefined
|
|
8682
9177
|
: resolvePlannerModelIdForGeneration(prompt, coderModelForRequest);
|
|
8683
9178
|
const runtimeLogsForRequest = collectRuntimeLogsForCodegen(generationWidgetId);
|
|
8684
|
-
|
|
9179
|
+
// The generated client types this call as void and drops the 202 body.
|
|
9180
|
+
// Read the raw JSON so operationId and the intent-gate payload survive.
|
|
9181
|
+
const accepted = await agentClient.agentCodeGeneratePostRaw({
|
|
8685
9182
|
generateCodeRequest: {
|
|
8686
9183
|
repositoryId: repoId,
|
|
8687
9184
|
prompt,
|
|
@@ -8694,8 +9191,15 @@ Requirements:
|
|
|
8694
9191
|
compileFixOnly: options.compileFixOnly === true,
|
|
8695
9192
|
skipIntentGate: options.skipIntentGate === true,
|
|
8696
9193
|
chipIntent: options.chipIntent,
|
|
9194
|
+
brandKit: brandKitForRequest(),
|
|
8697
9195
|
} as GenerateCodeRequest
|
|
8698
9196
|
});
|
|
9197
|
+
let response: { data?: Record<string, unknown>; error?: string; message?: string } | null = null;
|
|
9198
|
+
try {
|
|
9199
|
+
response = await accepted.raw.json();
|
|
9200
|
+
} catch (parseError) {
|
|
9201
|
+
console.warn('[WidgetCreator] Code generation response was not JSON', parseError);
|
|
9202
|
+
}
|
|
8699
9203
|
// 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);
|
|
8700
9204
|
// console.log('Widget Creator: Code generation response:', response);
|
|
8701
9205
|
|
|
@@ -8749,7 +9253,8 @@ Requirements:
|
|
|
8749
9253
|
return;
|
|
8750
9254
|
}
|
|
8751
9255
|
|
|
8752
|
-
|
|
9256
|
+
const tokenEnvelope = response as unknown as { error?: string } | undefined;
|
|
9257
|
+
if (tokenEnvelope?.error === 'TOKEN_EXPIRED') {
|
|
8753
9258
|
patchCodegenUiState(generationWidgetId, {
|
|
8754
9259
|
generateCodeStatus: '❌ Authentication expired. Please login again.',
|
|
8755
9260
|
lastGenerationFailed: true,
|
|
@@ -8779,9 +9284,9 @@ Requirements:
|
|
|
8779
9284
|
return;
|
|
8780
9285
|
}
|
|
8781
9286
|
|
|
8782
|
-
const generateData = (
|
|
9287
|
+
const generateData = (response?.data ?? response) as {
|
|
8783
9288
|
operationId?: string;
|
|
8784
|
-
};
|
|
9289
|
+
} | null;
|
|
8785
9290
|
const operationId = generateData?.operationId;
|
|
8786
9291
|
|
|
8787
9292
|
// The Worker serves rebuilt code at the same preview URL, so the panel
|
|
@@ -8864,6 +9369,7 @@ Requirements:
|
|
|
8864
9369
|
clearActiveOperationId();
|
|
8865
9370
|
|
|
8866
9371
|
const completedCommitSha = getOperationCommitId(op.metadata);
|
|
9372
|
+
const completedCommitTitle = getOperationCommitTitle(op.metadata);
|
|
8867
9373
|
const completedPreviewBuildErr = getOperationPreviewBuildError(op.metadata);
|
|
8868
9374
|
const completedCached = getOperationCached(op.metadata);
|
|
8869
9375
|
const completedCachedFiles = getOperationCachedFiles(op.metadata);
|
|
@@ -8884,6 +9390,11 @@ Requirements:
|
|
|
8884
9390
|
|
|
8885
9391
|
if (completedCommitSha && generationWidgetId) {
|
|
8886
9392
|
saveCommitIdForWidget(generationWidgetId, completedCommitSha);
|
|
9393
|
+
// The publish path stamps its optimistic row from the publish
|
|
9394
|
+
// response; a generation stamps this turn's draft row from the
|
|
9395
|
+
// operation, so the version menu shows what the turn shipped
|
|
9396
|
+
// without waiting for the history refetch below.
|
|
9397
|
+
stampCodegenTitleIntoPanelHistory(generationWidgetId, completedCommitSha, completedCommitTitle);
|
|
8887
9398
|
}
|
|
8888
9399
|
|
|
8889
9400
|
// Mark pending publish when this widget already has a live version.
|
|
@@ -8922,6 +9433,10 @@ Requirements:
|
|
|
8922
9433
|
const alignCommitSha = completedCommitSha;
|
|
8923
9434
|
const alignGeneratedVersion = (attempt: number) => {
|
|
8924
9435
|
void loadPublishHistoryForPanel(alignWidgetId, { silent: true }).then(() => {
|
|
9436
|
+
// The refetch is authoritative, but a snapshot that raced the
|
|
9437
|
+
// row's title (or an API row still missing it) gets the
|
|
9438
|
+
// operation's label — the guard inside keeps a user rename.
|
|
9439
|
+
stampCodegenTitleIntoPanelHistory(generationWidgetId, completedCommitSha, completedCommitTitle);
|
|
8925
9440
|
const versionId = findVersionIdMatchingCommit(alignWidgetId, alignCommitSha);
|
|
8926
9441
|
const matchedByCommit = !!alignCommitSha
|
|
8927
9442
|
&& !!versionId
|
|
@@ -9108,6 +9623,17 @@ Requirements:
|
|
|
9108
9623
|
);
|
|
9109
9624
|
}
|
|
9110
9625
|
|
|
9626
|
+
// Design-contract violations mean some design controls will silently
|
|
9627
|
+
// do nothing (the property isn't wired, or the widget never applies
|
|
9628
|
+
// design updates). Say which, right where the user is looking — a
|
|
9629
|
+
// control that looks live but is dead is the worst failure mode here.
|
|
9630
|
+
// Served from the operation metadata; the server also embeds it in the
|
|
9631
|
+
// persisted reply, so it survives a reload.
|
|
9632
|
+
const designContractWarning = getOperationDesignContractWarning(op.metadata);
|
|
9633
|
+
if (designContractWarning) {
|
|
9634
|
+
panelChat?.addAssistantMessage(designContractWarning, true);
|
|
9635
|
+
}
|
|
9636
|
+
|
|
9111
9637
|
setTimeout(async () => {
|
|
9112
9638
|
if (!getWidgetDetailsRef(generationWidgetId)) return;
|
|
9113
9639
|
const activePanelRef = getWidgetDetailsRef(generationWidgetId);
|
|
@@ -9154,6 +9680,7 @@ Requirements:
|
|
|
9154
9680
|
patchCodegenUiState(generationWidgetId, {
|
|
9155
9681
|
generateCodeStatus: `Code generation completed!${commitShort ? ` Commit: ${commitShort}` : ''} Preview ready.`,
|
|
9156
9682
|
});
|
|
9683
|
+
void refreshTrialCounter();
|
|
9157
9684
|
setTimeout(async () => {
|
|
9158
9685
|
await panelChat?.reloadConversationMessages?.();
|
|
9159
9686
|
panelChat?.markWidgetHeadAtLatestCommit?.();
|
|
@@ -9203,6 +9730,26 @@ Requirements:
|
|
|
9203
9730
|
return;
|
|
9204
9731
|
}
|
|
9205
9732
|
|
|
9733
|
+
// Handle retired-surface 410 (PLANNER_REMOVED / VM_REMOVED): the server
|
|
9734
|
+
// says this running bundle predates a removal — no retry can fix it.
|
|
9735
|
+
if (isRetiredSurfaceError(error)) {
|
|
9736
|
+
console.warn('Widget Creator: retired surface answered 410', { code: error.code, message: error.message });
|
|
9737
|
+
patchCodegenUiState(generationWidgetId, {
|
|
9738
|
+
generateCodeStatus: '⚠️ This app needs a refresh to continue.',
|
|
9739
|
+
lastGenerationFailed: true,
|
|
9740
|
+
lastGenerationError: error.message,
|
|
9741
|
+
});
|
|
9742
|
+
showToast('warning', 'This app needs a refresh', {
|
|
9743
|
+
description: error.message,
|
|
9744
|
+
duration: 12000,
|
|
9745
|
+
action: {
|
|
9746
|
+
label: 'Reload',
|
|
9747
|
+
onClick: () => window.location.reload()
|
|
9748
|
+
}
|
|
9749
|
+
});
|
|
9750
|
+
return;
|
|
9751
|
+
}
|
|
9752
|
+
|
|
9206
9753
|
// Handle rate limiting (429) — too many API requests in the time window
|
|
9207
9754
|
if (httpStatus === 429) {
|
|
9208
9755
|
console.warn('Widget Creator: Rate limited (429) during code generation');
|
|
@@ -9293,7 +9840,8 @@ Requirements:
|
|
|
9293
9840
|
widgetId: string,
|
|
9294
9841
|
version: number,
|
|
9295
9842
|
artifactUrl?: string | null,
|
|
9296
|
-
commitSha?: string | null
|
|
9843
|
+
commitSha?: string | null,
|
|
9844
|
+
commitTitle?: string | null
|
|
9297
9845
|
): void {
|
|
9298
9846
|
const jsPath = resolvePublishedJsPath(widgetId, version, artifactUrl);
|
|
9299
9847
|
const prevSnap = getPanelState(widgetId);
|
|
@@ -9317,13 +9865,36 @@ Requirements:
|
|
|
9317
9865
|
: w
|
|
9318
9866
|
);
|
|
9319
9867
|
|
|
9868
|
+
// The optimistic row replaces the refetch the publish flow used to wait
|
|
9869
|
+
// for, so it must carry every field the dropdown renders — not just the
|
|
9870
|
+
// sha/artifact pair. The title comes from the publish response (or the
|
|
9871
|
+
// WebSocket payload for cross-tab/background publishes); when the caller
|
|
9872
|
+
// had none, an already-known row for this version keeps its title rather
|
|
9873
|
+
// than being downgraded to untitled (the set-live path re-merges a row the
|
|
9874
|
+
// panel already displayed).
|
|
9875
|
+
const previousRow = prevSnap.publishHistory.find((r) => r.version === version);
|
|
9876
|
+
const titleFromCaller = typeof commitTitle === 'string' && commitTitle.trim() !== ''
|
|
9877
|
+
? commitTitle
|
|
9878
|
+
: null;
|
|
9879
|
+
const titleForEntry = titleFromCaller
|
|
9880
|
+
?? (previousRow?.commitTitle ?? previousRow?.commit_title ?? null);
|
|
9320
9881
|
const optimisticHistoryEntry = normalizedCommit
|
|
9321
9882
|
? {
|
|
9322
9883
|
version,
|
|
9323
9884
|
commitSha: normalizedCommit,
|
|
9324
9885
|
commit_sha: normalizedCommit,
|
|
9886
|
+
commitTitle: titleForEntry,
|
|
9887
|
+
commit_title: titleForEntry,
|
|
9325
9888
|
artifactUrl: artifactUrl ?? jsPath,
|
|
9326
9889
|
artifact_url: artifactUrl ?? jsPath,
|
|
9890
|
+
// Draft/Live split and timestamps: publishing this version is what
|
|
9891
|
+
// made it live, and a re-merge must not erase the original dates.
|
|
9892
|
+
publishedAt: previousRow?.publishedAt ?? previousRow?.published_at ?? new Date().toISOString(),
|
|
9893
|
+
published_at: previousRow?.publishedAt ?? previousRow?.published_at ?? new Date().toISOString(),
|
|
9894
|
+
createdAt: previousRow?.createdAt ?? previousRow?.created_at ?? new Date().toISOString(),
|
|
9895
|
+
created_at: previousRow?.createdAt ?? previousRow?.created_at ?? new Date().toISOString(),
|
|
9896
|
+
isPublished: true,
|
|
9897
|
+
is_published: true,
|
|
9327
9898
|
}
|
|
9328
9899
|
: null;
|
|
9329
9900
|
const mergedHistory = optimisticHistoryEntry
|
|
@@ -9379,6 +9950,44 @@ Requirements:
|
|
|
9379
9950
|
void loadPublishHistoryForPanel(widgetId, { silent: true });
|
|
9380
9951
|
}
|
|
9381
9952
|
|
|
9953
|
+
/**
|
|
9954
|
+
* Stamp the version title a codegen turn shipped onto that turn's history row.
|
|
9955
|
+
*
|
|
9956
|
+
* The publish path does this for its optimistic entry (the title rides the
|
|
9957
|
+
* publish response into `applyPublishSuccessToWidgetState`); a generation
|
|
9958
|
+
* only refetched the history, so the fresh draft row kept whatever title the
|
|
9959
|
+
* refetch returned — the operation's own label never reached the panel.
|
|
9960
|
+
*
|
|
9961
|
+
* Same discipline as the publish optimistic entry: the row the server already
|
|
9962
|
+
* wrote keeps its title (a user rename must not be reverted), and only the
|
|
9963
|
+
* row this turn *created* is stamped. Best-effort and synchronous — the
|
|
9964
|
+
* authoritative refetch that follows corrects anything this gets wrong.
|
|
9965
|
+
*/
|
|
9966
|
+
function stampCodegenTitleIntoPanelHistory(widgetId: string, commitSha: string | null, commitTitle: string | null): void {
|
|
9967
|
+
if (!commitSha || !commitTitle) return;
|
|
9968
|
+
|
|
9969
|
+
const snap = getPanelState(widgetId);
|
|
9970
|
+
const row = snap.publishHistory.find(
|
|
9971
|
+
(r) => (r.commitSha ?? r.commit_sha) === commitSha && !(r.publishedAt ?? r.published_at)
|
|
9972
|
+
);
|
|
9973
|
+
// The server's recordGeneration wrote this row with the same title; only a
|
|
9974
|
+
// stale snapshot makes the stamp a visible change. Never overwrite an
|
|
9975
|
+
// existing title (a user rename) with the pipeline's wording.
|
|
9976
|
+
const currentTitle = row?.commitTitle ?? row?.commit_title ?? null;
|
|
9977
|
+
if (!row || (currentTitle && currentTitle !== commitTitle)) return;
|
|
9978
|
+
|
|
9979
|
+
const history = snap.publishHistory.map((r) =>
|
|
9980
|
+
r === row
|
|
9981
|
+
? { ...r, commitTitle: commitTitle, commit_title: commitTitle }
|
|
9982
|
+
: r
|
|
9983
|
+
);
|
|
9984
|
+
patchPanelSnapshot(widgetId, { publishHistory: history });
|
|
9985
|
+
shapePublishHistoryByWidgetId = {
|
|
9986
|
+
...shapePublishHistoryByWidgetId,
|
|
9987
|
+
[widgetId]: [...history],
|
|
9988
|
+
};
|
|
9989
|
+
}
|
|
9990
|
+
|
|
9382
9991
|
/**
|
|
9383
9992
|
* Publish the current widget: saves first, then triggers the production build job.
|
|
9384
9993
|
* Flow: Save → fetch latest component → POST /canvases/{id}/components/{id}/publish
|
|
@@ -9506,10 +10115,11 @@ Requirements:
|
|
|
9506
10115
|
setCrossTabPublishing(false, capturedWidgetId);
|
|
9507
10116
|
|
|
9508
10117
|
const publishEnvelope = publishData as unknown as {
|
|
9509
|
-
data?: { version?: number; artifactUrl?: string | null; commitSha?: string | null };
|
|
10118
|
+
data?: { version?: number; artifactUrl?: string | null; commitSha?: string | null; commitTitle?: string | null };
|
|
9510
10119
|
version?: number;
|
|
9511
10120
|
artifactUrl?: string | null;
|
|
9512
10121
|
commitSha?: string | null;
|
|
10122
|
+
commitTitle?: string | null;
|
|
9513
10123
|
};
|
|
9514
10124
|
const version = publishEnvelope?.data?.version || publishEnvelope?.version || 1;
|
|
9515
10125
|
const artifactUrl = publishEnvelope?.data?.artifactUrl || publishEnvelope?.artifactUrl || null;
|
|
@@ -9518,7 +10128,13 @@ Requirements:
|
|
|
9518
10128
|
?? publishEnvelope?.commitSha
|
|
9519
10129
|
?? commitShaForPublish
|
|
9520
10130
|
?? null;
|
|
9521
|
-
|
|
10131
|
+
// The version row's title, returned so the optimistic history entry can
|
|
10132
|
+
// show the real label instead of waiting for the history refetch.
|
|
10133
|
+
const commitTitle =
|
|
10134
|
+
publishEnvelope?.data?.commitTitle
|
|
10135
|
+
?? publishEnvelope?.commitTitle
|
|
10136
|
+
?? null;
|
|
10137
|
+
applyPublishSuccessToWidgetState(capturedWidgetId, version, artifactUrl, commitSha, commitTitle);
|
|
9522
10138
|
|
|
9523
10139
|
if (!publishOpts.silent) {
|
|
9524
10140
|
showToast('success', 'Widget published!', {
|
|
@@ -9537,10 +10153,8 @@ Requirements:
|
|
|
9537
10153
|
publishedPanelRef?.invalidatePublishedArtifactCache?.();
|
|
9538
10154
|
await publishedPanelRef?.refreshPreview({ withRetry: true });
|
|
9539
10155
|
|
|
9540
|
-
if (!publishOpts.silent) {
|
|
9541
|
-
isHistoryOpen = true;
|
|
9542
|
-
}
|
|
9543
10156
|
await loadPublishHistory();
|
|
10157
|
+
void refreshTrialCounter();
|
|
9544
10158
|
|
|
9545
10159
|
} catch (err: any) {
|
|
9546
10160
|
console.error('[Publish] Error publishing widget:', err);
|
|
@@ -9548,10 +10162,18 @@ Requirements:
|
|
|
9548
10162
|
isPublishing = false;
|
|
9549
10163
|
publishingWidgetId = null;
|
|
9550
10164
|
setCrossTabPublishing(false, capturedWidgetId);
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
10165
|
+
const publishErrorText = normalizeErrorMessage(err) || 'An unexpected error occurred. Please try again.';
|
|
10166
|
+
if (isPlanLimitMessage(publishErrorText) && capturedWidgetId) {
|
|
10167
|
+
publishPlanMessageByWidgetId = {
|
|
10168
|
+
...publishPlanMessageByWidgetId,
|
|
10169
|
+
[capturedWidgetId]: publishErrorText,
|
|
10170
|
+
};
|
|
10171
|
+
} else {
|
|
10172
|
+
showToast('error', 'Publish failed', {
|
|
10173
|
+
description: publishErrorText,
|
|
10174
|
+
duration: 8000
|
|
10175
|
+
});
|
|
10176
|
+
}
|
|
9555
10177
|
}
|
|
9556
10178
|
// isPublishing stays true until widget_publish_status WebSocket event arrives (success or failure)
|
|
9557
10179
|
}
|
|
@@ -9930,6 +10552,13 @@ Requirements:
|
|
|
9930
10552
|
return typeof raw === 'string' && /^[a-f0-9]{7,40}$/i.test(raw) ? raw : null;
|
|
9931
10553
|
}
|
|
9932
10554
|
|
|
10555
|
+
/** The version title a completed codegen operation shipped ("Update header styles"), from operation metadata. */
|
|
10556
|
+
function getOperationCommitTitle(metadata?: Record<string, unknown>): string | null {
|
|
10557
|
+
if (!metadata) return null;
|
|
10558
|
+
const raw = metadata.commitTitle ?? metadata.commit_title;
|
|
10559
|
+
return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : null;
|
|
10560
|
+
}
|
|
10561
|
+
|
|
9933
10562
|
function getOperationPreviewBuildError(metadata?: Record<string, unknown>): string | null {
|
|
9934
10563
|
if (!metadata) return null;
|
|
9935
10564
|
const raw = metadata.previewBuildError ?? metadata.preview_build_error;
|
|
@@ -9937,6 +10566,17 @@ Requirements:
|
|
|
9937
10566
|
return sanitizePreviewBuildError(raw);
|
|
9938
10567
|
}
|
|
9939
10568
|
|
|
10569
|
+
/**
|
|
10570
|
+
* The short design-contract warning the codegen service attached to a
|
|
10571
|
+
* completed operation, naming the design properties that will silently do
|
|
10572
|
+
* nothing. Null when the contract was clean (or nothing checked it).
|
|
10573
|
+
*/
|
|
10574
|
+
function getOperationDesignContractWarning(metadata?: Record<string, unknown>): string | null {
|
|
10575
|
+
if (!metadata) return null;
|
|
10576
|
+
const raw = metadata.designContractWarning ?? metadata.design_contract_warning;
|
|
10577
|
+
return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : null;
|
|
10578
|
+
}
|
|
10579
|
+
|
|
9940
10580
|
/** True when the operation completed from the semantic cache (no new generation ran). */
|
|
9941
10581
|
function getOperationCached(metadata?: Record<string, unknown>): boolean {
|
|
9942
10582
|
return metadata?.cached === true;
|
|
@@ -10185,8 +10825,8 @@ Requirements:
|
|
|
10185
10825
|
liveDesignValues: Record<string, unknown>;
|
|
10186
10826
|
liveContentValues: Record<string, unknown>;
|
|
10187
10827
|
liveContentItems: any[];
|
|
10188
|
-
compositionDesignSchema:
|
|
10189
|
-
compositionContentSchema:
|
|
10828
|
+
compositionDesignSchema: DesignSchemaView | null;
|
|
10829
|
+
compositionContentSchema: ContentSchemaView | null;
|
|
10190
10830
|
compositions: any[];
|
|
10191
10831
|
selectedComposition: any;
|
|
10192
10832
|
compositionDirty: boolean;
|
|
@@ -10809,8 +11449,10 @@ Requirements:
|
|
|
10809
11449
|
return null;
|
|
10810
11450
|
}
|
|
10811
11451
|
|
|
10812
|
-
//
|
|
10813
|
-
|
|
11452
|
+
// 'preview' is the explicit post-codegen source. An unpublished commit must
|
|
11453
|
+
// not replace Live: on open the dropdown stays on the published version,
|
|
11454
|
+
// and this URL has to be that same artifact.
|
|
11455
|
+
if (source === 'preview') {
|
|
10814
11456
|
if (workerUrl) return workerUrl;
|
|
10815
11457
|
}
|
|
10816
11458
|
|
|
@@ -10838,7 +11480,9 @@ Requirements:
|
|
|
10838
11480
|
if (newestDraft != null) return `v${newestDraft}`;
|
|
10839
11481
|
return null;
|
|
10840
11482
|
}
|
|
10841
|
-
|
|
11483
|
+
if (liveVersion != null) return `v${liveVersion}`;
|
|
11484
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
11485
|
+
return newestDraft != null ? `v${newestDraft}` : null;
|
|
10842
11486
|
}
|
|
10843
11487
|
|
|
10844
11488
|
function formatPublishHistoryDate(record: {
|
|
@@ -10939,7 +11583,9 @@ Requirements:
|
|
|
10939
11583
|
const ps = getPanelDisplayState(widgetId);
|
|
10940
11584
|
const liveVersion =
|
|
10941
11585
|
ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(getWidgetById(widgetId));
|
|
10942
|
-
|
|
11586
|
+
if (liveVersion != null) return `v:${liveVersion}`;
|
|
11587
|
+
const newestDraft = getNewestDraftVersionId(widgetId);
|
|
11588
|
+
return newestDraft != null ? `v:${newestDraft}` : null;
|
|
10943
11589
|
}
|
|
10944
11590
|
|
|
10945
11591
|
function selectVersionInDropdowns(widgetId: string, versionId: string): void {
|
|
@@ -11669,7 +12315,7 @@ Requirements:
|
|
|
11669
12315
|
? getWidgetDetailsRef(selectedWidgetId)
|
|
11670
12316
|
: null;
|
|
11671
12317
|
|
|
11672
|
-
function getWidgetDetailsRef(widgetId?: string | null):
|
|
12318
|
+
function getWidgetDetailsRef(widgetId?: string | null): WidgetDetailsInstance | null {
|
|
11673
12319
|
const id = widgetId ?? focusedPanelWidgetId ?? selectedWidgetId;
|
|
11674
12320
|
if (!id) return null;
|
|
11675
12321
|
const instanceKey = panelInstanceKeyByWidgetId[id] ?? id;
|
|
@@ -12592,7 +13238,9 @@ $: if ($widgetPublishStatus.timestamp && $widgetPublishStatus.widgetId) {
|
|
|
12592
13238
|
applyPublishSuccessToWidgetState(
|
|
12593
13239
|
publishWidgetId,
|
|
12594
13240
|
publishedVersion,
|
|
12595
|
-
publishResult.artifactUrl ?? null
|
|
13241
|
+
publishResult.artifactUrl ?? null,
|
|
13242
|
+
publishResult.commitSha ?? null,
|
|
13243
|
+
publishResult.commitTitle ?? null
|
|
12596
13244
|
);
|
|
12597
13245
|
const publishedName = getWidgetById(publishWidgetId)?.name || 'Widget';
|
|
12598
13246
|
showToast('success', `"${publishedName}" published!`, {
|
|
@@ -12810,7 +13458,7 @@ async function setLiveVersionForWidget(widgetId: string, version: number) {
|
|
|
12810
13458
|
widgetId,
|
|
12811
13459
|
version
|
|
12812
13460
|
});
|
|
12813
|
-
const nested = liveRes?.data as { data?: Record<string, unknown>; artifactUrl?: string; artifact_url?: string; commitSha?: string; commit_sha?: string } | undefined;
|
|
13461
|
+
const nested = liveRes?.data as { data?: Record<string, unknown>; artifactUrl?: string; artifact_url?: string; commitSha?: string; commit_sha?: string; commitTitle?: string; commit_title?: string } | undefined;
|
|
12814
13462
|
const liveData = nested?.data ?? nested ?? {};
|
|
12815
13463
|
const history = getPanelDisplayState(widgetId).publishHistory;
|
|
12816
13464
|
const record = history.find((row) => row.version === version);
|
|
@@ -12820,7 +13468,12 @@ async function setLiveVersionForWidget(widgetId: string, version: number) {
|
|
|
12820
13468
|
const artifactUrl = (liveData as { artifactUrl?: string; artifact_url?: string }).artifactUrl
|
|
12821
13469
|
|| (liveData as { artifact_url?: string }).artifact_url
|
|
12822
13470
|
|| record?.artifactUrl || record?.artifact_url || null;
|
|
12823
|
-
|
|
13471
|
+
// The row's current title — set-live re-merges a row the panel already
|
|
13472
|
+
// showed, so the response value keeps the label without a refetch.
|
|
13473
|
+
const commitTitle = (liveData as { commitTitle?: string; commit_title?: string }).commitTitle
|
|
13474
|
+
|| (liveData as { commit_title?: string }).commit_title
|
|
13475
|
+
|| record?.commitTitle || record?.commit_title || null;
|
|
13476
|
+
applyPublishSuccessToWidgetState(widgetId, version, artifactUrl, commitSha, commitTitle);
|
|
12824
13477
|
shapePreviewSourceByWidgetId = {
|
|
12825
13478
|
...shapePreviewSourceByWidgetId,
|
|
12826
13479
|
[widgetId]: `v:${version}`,
|
|
@@ -12847,6 +13500,63 @@ async function setLiveVersionForWidget(widgetId: string, version: number) {
|
|
|
12847
13500
|
}
|
|
12848
13501
|
}
|
|
12849
13502
|
|
|
13503
|
+
/**
|
|
13504
|
+
* Renames one version's title — the owner's correction of the wording the
|
|
13505
|
+
* pipeline authored. Optimistic on the row; a failure rolls the row back
|
|
13506
|
+
* and says so in a toast.
|
|
13507
|
+
*/
|
|
13508
|
+
async function renameVersionTitleForWidget(widgetId: string, version: number, title: string) {
|
|
13509
|
+
if (!widgetId || !$userSession?.jwt) return;
|
|
13510
|
+
if (!widgetsClient) {
|
|
13511
|
+
showToast('error', 'Widgets API client is not configured');
|
|
13512
|
+
return;
|
|
13513
|
+
}
|
|
13514
|
+
|
|
13515
|
+
const renameKey = `${widgetId}:${version}`;
|
|
13516
|
+
renamingTitleFor = renameKey;
|
|
13517
|
+
|
|
13518
|
+
// Optimistic: write the trimmed title into the row immediately so the
|
|
13519
|
+
// dropdown does not flicker while the PATCH is in flight.
|
|
13520
|
+
const history = getPanelDisplayState(widgetId).publishHistory;
|
|
13521
|
+
const record = history.find((row) => Number(row.version) === version);
|
|
13522
|
+
const previousTitle = record
|
|
13523
|
+
? (record.commitTitle ?? record.commit_title ?? null)
|
|
13524
|
+
: null;
|
|
13525
|
+
if (record) {
|
|
13526
|
+
record.commitTitle = title === '' ? null : title;
|
|
13527
|
+
record.commit_title = title === '' ? null : title;
|
|
13528
|
+
}
|
|
13529
|
+
panelDisplayRevision += 1;
|
|
13530
|
+
|
|
13531
|
+
try {
|
|
13532
|
+
const res = await widgetsClient.renameWidgetPublishHistoryTitle({
|
|
13533
|
+
widgetId,
|
|
13534
|
+
version,
|
|
13535
|
+
renameWidgetPublishHistoryTitleRequest: { commitTitle: title },
|
|
13536
|
+
});
|
|
13537
|
+
const stored = (res as { data?: { commitTitle?: string | null } }).data?.commitTitle;
|
|
13538
|
+
// The stored value is authoritative (server trims and bounds).
|
|
13539
|
+
if (record) {
|
|
13540
|
+
record.commitTitle = stored ?? null;
|
|
13541
|
+
record.commit_title = stored ?? null;
|
|
13542
|
+
}
|
|
13543
|
+
panelDisplayRevision += 1;
|
|
13544
|
+
showToast('success', stored ? 'Version renamed' : 'Version title cleared');
|
|
13545
|
+
} catch (err) {
|
|
13546
|
+
// Roll the row back; nothing else needs reloading — the write did not land.
|
|
13547
|
+
if (record) {
|
|
13548
|
+
record.commitTitle = previousTitle;
|
|
13549
|
+
record.commit_title = previousTitle;
|
|
13550
|
+
}
|
|
13551
|
+
panelDisplayRevision += 1;
|
|
13552
|
+
showToast('error', 'Could not rename the version', {
|
|
13553
|
+
description: err instanceof ResponseError ? 'The server refused the rename.' : 'Check your connection and try again.',
|
|
13554
|
+
});
|
|
13555
|
+
} finally {
|
|
13556
|
+
renamingTitleFor = null;
|
|
13557
|
+
}
|
|
13558
|
+
}
|
|
13559
|
+
|
|
12850
13560
|
async function setLiveVersion(version: number) {
|
|
12851
13561
|
if (!selectedWidgetId) return;
|
|
12852
13562
|
await setLiveVersionForWidget(selectedWidgetId, version);
|
|
@@ -12962,8 +13672,12 @@ $: if (selectedWidgetId && autoGenDialogCheckedForWidgetId !== selectedWidgetId)
|
|
|
12962
13672
|
}
|
|
12963
13673
|
if (isGeneratingCode || confirmGenerateDialogOpen) return;
|
|
12964
13674
|
const widgetForDialog = getWidgetById(capturedWidgetId);
|
|
13675
|
+
const panelSnap = getPanelState(capturedWidgetId);
|
|
12965
13676
|
const hasRepo = !!normalizeRepositoryId(widgetForDialog);
|
|
12966
|
-
const
|
|
13677
|
+
const publishedVersion = panelSnap.lastPublishedVersion ?? resolvePublishedVersionFromWidget(widgetForDialog);
|
|
13678
|
+
const hasNoCode = !panelSnap.lastCommitId
|
|
13679
|
+
&& publishedVersion == null
|
|
13680
|
+
&& (panelSnap.publishHistory?.length ?? 0) === 0;
|
|
12967
13681
|
const hasPrompt = codeGenerationPrompt !== DEFAULT_PROMPT || promptImages.length > 0;
|
|
12968
13682
|
const hasPreview = widgetDetails?.hasValidPreview() ?? false;
|
|
12969
13683
|
if (hasRepo && hasNoCode && !hasPreview && hasPrompt) {
|
|
@@ -15014,6 +15728,18 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15014
15728
|
|
|
15015
15729
|
<!-- Right: Debug toggle + Auth info (2 lines) + Logout -->
|
|
15016
15730
|
<div class="wc-top-bar-right flex items-center gap-3 shrink-0">
|
|
15731
|
+
{#if trialCounter}
|
|
15732
|
+
<span
|
|
15733
|
+
class="wc-trial-counter shrink-0 whitespace-nowrap rounded-md border border-[#314F2F]/20 bg-[#f4f7f4] px-2 py-1 text-[11px] font-medium text-[#314F2F]"
|
|
15734
|
+
title={trialCounter.days == null ? 'Credits left on this account' : 'Free trial: credits left and days left'}
|
|
15735
|
+
>
|
|
15736
|
+
{trialCounter.credits} credits{trialCounter.trialEnded
|
|
15737
|
+
? ' · trial ended'
|
|
15738
|
+
: trialCounter.days == null
|
|
15739
|
+
? ''
|
|
15740
|
+
: ` · ${trialCounter.days} ${trialCounter.days === 1 ? 'day' : 'days'}`}
|
|
15741
|
+
</span>
|
|
15742
|
+
{/if}
|
|
15017
15743
|
{#if debugModeAvailable}
|
|
15018
15744
|
<HoverTooltip text={debugMode ? 'Debug mode ON — click to disable (hides console tab, widgets panel)' : 'Debug mode OFF — click to enable'}>
|
|
15019
15745
|
<button
|
|
@@ -15103,6 +15829,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15103
15829
|
canvasComponentReady = false;
|
|
15104
15830
|
}}
|
|
15105
15831
|
onConvertToWidget={handleConvertToWidget}
|
|
15832
|
+
onRecognizeStroke={handleRecognizeStroke}
|
|
15106
15833
|
onWidgetImageClick={handleWidgetImageClick}
|
|
15107
15834
|
onCanvasDirty={() => emitCreatorSaveState(true)}
|
|
15108
15835
|
onWidgetShapeDuplicated={handleWidgetShapeDuplicated}
|
|
@@ -15118,8 +15845,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15118
15845
|
if (suppressWidgetDetailsClose || isConvertOrDraftPanelOpen()) {
|
|
15119
15846
|
// console.log('[CreatorApp] Skip Widget Details close — convert/draft panel is open');
|
|
15120
15847
|
return;
|
|
15121
|
-
}
|
|
15122
|
-
const active = canvasRef?.getSelectedObject?.() ?? canvasRef?.selectedObject;
|
|
15848
|
+
} const active = canvasRef?.getSelectedObject?.();
|
|
15123
15849
|
if (active?._isWidgetImage && active?._widgetId) {
|
|
15124
15850
|
return;
|
|
15125
15851
|
}
|
|
@@ -15511,7 +16237,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15511
16237
|
{publishWidgetStatus}
|
|
15512
16238
|
publishNeedsSaveFirst={publishNeedsSaveFirstForPanel(panelWidgetId)}
|
|
15513
16239
|
lastPublishedVersion={isDraftConvertId(panelWidgetId) ? null : (ps.lastPublishedVersion ?? resolvePublishedVersionFromWidget(panelWidget ?? null))}
|
|
15514
|
-
widgetJsPath={ps.selectedWidgetJsPath ?? panelWidget?.jsPath
|
|
16240
|
+
widgetJsPath={ps.selectedWidgetJsPath ?? (typeof panelWidget?.jsPath === 'string' ? panelWidget.jsPath : null)}
|
|
15515
16241
|
{isGeneratingInOtherTab}
|
|
15516
16242
|
{isPublishingInOtherTab}
|
|
15517
16243
|
{isSettingLiveVersion}
|
|
@@ -15913,7 +16639,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15913
16639
|
<Portal target="wc-live-history-portal-{panelWidgetId}">
|
|
15914
16640
|
<button
|
|
15915
16641
|
type="button"
|
|
15916
|
-
class="wc-live-history-backdrop fixed
|
|
16642
|
+
class="wc-live-history-backdrop fixed left-0 right-0 bottom-0 top-14 z-[9998] cursor-default bg-transparent"
|
|
15917
16643
|
aria-label="Close version history"
|
|
15918
16644
|
onclick={() => { isHistoryOpen = false; }}
|
|
15919
16645
|
></button>
|
|
@@ -15937,12 +16663,11 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
15937
16663
|
{@const rowIsViewing = rowVersion === Number(viewingVersion)}
|
|
15938
16664
|
{@const rowIsLive = !rowIsDraft && rowVersion === Number(panelPublishedVersion)}
|
|
15939
16665
|
{@const rowDateLabel = formatPublishHistoryDate(record)}
|
|
15940
|
-
{@const publishGate = getPublishGateForPanel(panelWidgetId, rowVersion)}
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
dateLabel={rowDateLabel}
|
|
16666
|
+
{@const publishGate = getPublishGateForPanel(panelWidgetId, rowVersion)} <VersionDropdownRow
|
|
16667
|
+
versionLabel={'v' + rowVersion}
|
|
16668
|
+
commitSha={record.commitSha ?? record.commit_sha ?? null}
|
|
16669
|
+
showCommit={debugMode}
|
|
16670
|
+
dateLabel={rowDateLabel}
|
|
15946
16671
|
isLive={rowIsLive}
|
|
15947
16672
|
isDraft={rowIsDraft}
|
|
15948
16673
|
selected={rowIsViewing}
|
|
@@ -16008,10 +16733,47 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16008
16733
|
{/if}
|
|
16009
16734
|
</div>
|
|
16010
16735
|
</div>
|
|
16736
|
+
{#if publishPlanMessageByWidgetId[panelWidgetId]}
|
|
16737
|
+
<div class="publish-plan-notice p-3 bg-red-50 border border-red-200" style="border-radius: 0;">
|
|
16738
|
+
<p class="text-xs text-red-700 whitespace-pre-wrap">{publishPlanMessageByWidgetId[panelWidgetId]}</p>
|
|
16739
|
+
<div class="flex gap-2 mt-3 justify-center">
|
|
16740
|
+
<Button
|
|
16741
|
+
onclick={openUpgradePlans}
|
|
16742
|
+
variant="default"
|
|
16743
|
+
size="sm"
|
|
16744
|
+
class="bg-red-600 hover:bg-red-700 text-white rounded-lg"
|
|
16745
|
+
>
|
|
16746
|
+
Upgrade
|
|
16747
|
+
</Button>
|
|
16748
|
+
<Button
|
|
16749
|
+
onclick={() => {
|
|
16750
|
+
const next = { ...publishPlanMessageByWidgetId };
|
|
16751
|
+
delete next[panelWidgetId];
|
|
16752
|
+
publishPlanMessageByWidgetId = next;
|
|
16753
|
+
}}
|
|
16754
|
+
variant="outline"
|
|
16755
|
+
size="sm"
|
|
16756
|
+
class="border-gray-300 text-gray-600 hover:bg-gray-50 rounded-lg"
|
|
16757
|
+
>
|
|
16758
|
+
Dismiss
|
|
16759
|
+
</Button>
|
|
16760
|
+
</div>
|
|
16761
|
+
</div>
|
|
16762
|
+
{/if}
|
|
16011
16763
|
{#if ps.lastGenerationFailed}
|
|
16012
16764
|
<div class="p-3 bg-red-50 border border-red-200" style="border-radius: 0;">
|
|
16013
16765
|
<p class="text-xs text-red-700 whitespace-pre-wrap">{ps.lastGenerationError}</p>
|
|
16014
16766
|
<div class="flex gap-2 mt-3 justify-center">
|
|
16767
|
+
{#if isPlanLimitMessage(ps.lastGenerationError)}
|
|
16768
|
+
<Button
|
|
16769
|
+
onclick={openUpgradePlans}
|
|
16770
|
+
variant="default"
|
|
16771
|
+
size="sm"
|
|
16772
|
+
class="bg-red-600 hover:bg-red-700 text-white rounded-lg"
|
|
16773
|
+
>
|
|
16774
|
+
Upgrade
|
|
16775
|
+
</Button>
|
|
16776
|
+
{:else}
|
|
16015
16777
|
<Button
|
|
16016
16778
|
onclick={() => {
|
|
16017
16779
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
@@ -16026,6 +16788,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16026
16788
|
>
|
|
16027
16789
|
Retry
|
|
16028
16790
|
</Button>
|
|
16791
|
+
{/if}
|
|
16029
16792
|
<Button
|
|
16030
16793
|
onclick={() => {
|
|
16031
16794
|
if (!isPanelFocused) focusWidgetPanel(panelWidgetId);
|
|
@@ -16070,9 +16833,50 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16070
16833
|
? `flex: 1 1 auto; min-height: ${CHAT_AUTO_MIN_HEIGHT}px;`
|
|
16071
16834
|
: `flex: 0 0 ${chatUserHeight}px; min-height: ${CHAT_MIN_HEIGHT}px; height: ${chatUserHeight}px;`}
|
|
16072
16835
|
>
|
|
16836
|
+
<!-- Cast to any below: @widgetic/chat is type-checked against its own
|
|
16837
|
+
Svelte install, so its Snippet type is nominally distinct from this
|
|
16838
|
+
package's. renderDesignControls is a real snippet either way. -->
|
|
16839
|
+
{#snippet renderDesignControls(spec)}
|
|
16840
|
+
<!-- `ps` is rebuilt on every panel display revision, so an attached
|
|
16841
|
+
card re-reads `liveValues` and follows a drag made in the
|
|
16842
|
+
PropsEditor panel; the card's own edits come back through the
|
|
16843
|
+
same live state via `onChange`. A persisted card only gets that
|
|
16844
|
+
live state while its own composition is the one loaded. -->
|
|
16845
|
+
{@const cardAttached = isDesignCardAttached(spec, ps.selectedComposition?.id ?? null)}
|
|
16846
|
+
<DesignQuickEdit
|
|
16847
|
+
{spec}
|
|
16848
|
+
liveValues={cardAttached ? ps.liveDesignValues : undefined}
|
|
16849
|
+
knownPropertyIds={cardAttached ? designPropertyIdsFor(ps.compositionDesignSchema) : undefined}
|
|
16850
|
+
detached={!cardAttached}
|
|
16851
|
+
onChange={(id, value) => applyDesignValuesToPreview(panelWidgetId, { [id]: value })}
|
|
16852
|
+
/>
|
|
16853
|
+
{/snippet}
|
|
16854
|
+
{#snippet generationOptionChecks()}
|
|
16855
|
+
<BrandKitPanel
|
|
16856
|
+
part="checks"
|
|
16857
|
+
showPresetLibraries={debugMode}
|
|
16858
|
+
suspendPickers={!!generatingWidgetIds[panelWidgetId]}
|
|
16859
|
+
bind:brandKitEnabled
|
|
16860
|
+
bind:librariesEnabled
|
|
16861
|
+
bind:libraries={brandKitLibraries}
|
|
16862
|
+
bind:libraryGitUrl
|
|
16863
|
+
bind:libraryFileName
|
|
16864
|
+
bind:libraryUploads
|
|
16865
|
+
bind:fontUploads
|
|
16866
|
+
bind:primary={brandPrimary}
|
|
16867
|
+
bind:secondary={brandSecondary}
|
|
16868
|
+
bind:title={brandTitle}
|
|
16869
|
+
bind:sub={brandSub}
|
|
16870
|
+
bind:fonts={brandFonts}
|
|
16871
|
+
showLanguage={debugMode}
|
|
16872
|
+
bind:language={widgetLanguage}
|
|
16873
|
+
/>
|
|
16874
|
+
{/snippet}
|
|
16073
16875
|
<Chat class="code-generation-chat w-full h-full rounded-none border-0"
|
|
16876
|
+
actionBarExtras={generationOptionChecks}
|
|
16074
16877
|
style="height: 100%; min-height: {chatUserHeight == null ? CHAT_AUTO_MIN_HEIGHT : CHAT_MIN_HEIGHT}px;"
|
|
16075
16878
|
resizeExpandsContainer={true}
|
|
16879
|
+
renderDesignControls={renderDesignControls as any}
|
|
16076
16880
|
onMessagesResize={(deltaPx) => handleChatMessagesResize(panelWidgetId, deltaPx)}
|
|
16077
16881
|
showLogs={debugMode}
|
|
16078
16882
|
showHeader={false}
|
|
@@ -16094,6 +16898,7 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16094
16898
|
// Debug-only surfaces: assistant "Updated N files: ..." summaries
|
|
16095
16899
|
// and heuristic code-block auto-detection. Hidden in production.
|
|
16096
16900
|
showGenerationSummaries: debugMode,
|
|
16901
|
+
markComplete: false,
|
|
16097
16902
|
// Keep false — debug autoCodeDetection mislabels "Code generation failed: …" as JavaScript.
|
|
16098
16903
|
autoCodeDetection: false,
|
|
16099
16904
|
}
|
|
@@ -16111,10 +16916,9 @@ $: if (codeGenerationPrompt && selectedWidgetId && promptLoadedForWidgetId === s
|
|
|
16111
16916
|
onCompletionToggle={(detail) => handleChatCompletionToggle(panelWidgetId, detail)}
|
|
16112
16917
|
onLoadConversations={handleLoadConversations}
|
|
16113
16918
|
onLoadMessages={(conversationId) => handleLoadMessages(conversationId, panelWidgetId)}
|
|
16114
|
-
onCreateConversation={handleCreateConversation}
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
}
|
|
16919
|
+
onCreateConversation={handleCreateConversation} onSaveMessage={(conversationId, content, messageType, attachments, suggestions, designControls) =>
|
|
16920
|
+
handleSaveMessage(conversationId, content, messageType, attachments, suggestions, panelWidgetId, designControls)
|
|
16921
|
+
}
|
|
16118
16922
|
onRestoreCheckpointBackend={(conversationId, checkpointMessageId) =>
|
|
16119
16923
|
handleRestoreCheckpoint(conversationId, checkpointMessageId, panelWidgetId)
|
|
16120
16924
|
}
|