@designcrowd/library.brand-page 6.0.1-jj-6 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-X6T8_3JP.js → CreatedOnBrandCrowd-de-de-BPP1Wtpt.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-BC59Ss9F.js → CreatedOnBrandCrowd-en-us-BlfpQiIg.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BF1QReGi.js → CreatedOnBrandCrowd-es-es-Tldy_pIo.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-BhI-30CT.js → CreatedOnBrandCrowd-fr-ca-CHmb3VQE.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-BhI-30CT.js → CreatedOnBrandCrowd-fr-fr-CHmb3VQE.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-BJQkhGhh.js → CreatedOnBrandCrowd-pt-br-XbU3eK3Z.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-BJQkhGhh.js → CreatedOnBrandCrowd-pt-pt-XbU3eK3Z.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-pQfxLfUx.js → CreatedOnDcom-de-de-BVMFbVbI.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DQWSlN-g.js → CreatedOnDcom-en-us--kaKb1kI.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-B5oZRbLp.js → CreatedOnDcom-es-es-XXVLpF2I.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BBhSUN6J.js → CreatedOnDcom-fr-ca-D-7vtMZ5.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-CXc2On48.js → CreatedOnDcom-fr-fr-CM9oQk77.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-B5AmbldX.js → CreatedOnDcom-pt-br-VVgu3BEa.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-Df_mxV0U.js → CreatedOnDcom-pt-pt-CWhA8Vla.js} +1 -1
  15. package/lib/src/brand-page-maker/components/admin-background-settings/ShapePositionSlider.mixin.d.ts +1 -1
  16. package/lib/src/brand-page-maker/composables/ai-chat/types.d.ts +0 -3
  17. package/lib/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.js +171 -203
  18. package/lib/src/brand-page-maker/composables/useEventTracker.d.ts +0 -1
  19. package/lib/src/brand-page-maker/composables/useEventTracker.js +0 -1
  20. package/lib/src/brand-page-maker/constants/config-settings-constant.js +2 -2
  21. package/lib/src/clients/ai-assistant-client.d.ts +0 -7
  22. package/lib/src/css/library.brand-page-brandCrowd.min.css +27 -36
  23. package/lib/src/css/library.brand-page-brandPage.min.css +27 -36
  24. package/lib/src/css/library.brand-page-designCom.min.css +27 -36
  25. package/lib/src/css/library.brand-page-preview.min.css +27 -36
  26. package/lib/src/index.d.ts +0 -4
  27. package/lib/src/index.mjs +104 -107
  28. package/lib/src/{lib-DfeZjykC.js → lib-yEV1yYI5.js} +20614 -20919
  29. package/lib/src/lib.js +0 -10
  30. package/lib/src/metadata-builder/ai-metadata-executor.js +1 -24
  31. package/lib/src/metadata-builder/builders/section-builders/move-section-builder.d.ts +1 -13
  32. package/lib/src/metadata-builder/builders/section-builders/move-section-builder.js +30 -40
  33. package/lib/src/metadata-builder/builders/section-field-maps.js +0 -17
  34. package/lib/src/metadata-builder/metadata-executor.js +0 -4
  35. package/lib/src/metadata-builder/models.d.ts +0 -2
  36. package/lib/src/metadata-builder/models.js +0 -2
  37. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +3 -18
  38. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +0 -2
  39. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +0 -18
  40. package/lib/src/utils/build-asset-manifest.d.ts +0 -4
  41. package/lib/src/utils/build-asset-manifest.js +0 -8
  42. package/lib/src/utils/minify-brand-page-metadata.js +9 -1
  43. package/package.json +2 -2
  44. package/src/brand-page/components/shop/Shop.vue +1 -0
  45. package/src/brand-page-maker/components/ai-text-edit/AiActionButtons.vue +12 -27
  46. package/src/brand-page-maker/components/ai-text-edit/AiChatbox.vue +2 -3
  47. package/src/brand-page-maker/components/ai-text-edit/TextBubble.vue +7 -7
  48. package/src/brand-page-maker/components/ai-website-builder/AiWebsiteBuilder.vue +1 -21
  49. package/src/brand-page-maker/composables/ai-chat/types.ts +0 -6
  50. package/src/brand-page-maker/composables/ai-chat/useAiWebsiteBuilderChat.ts +255 -307
  51. package/src/brand-page-maker/composables/useEventTracker.ts +0 -1
  52. package/src/brand-page-maker/constants/config-settings-constant.ts +2 -2
  53. package/src/clients/ai-assistant-client.ts +0 -7
  54. package/src/lib.ts +0 -12
  55. package/src/metadata-builder/ai-metadata-executor.ts +1 -31
  56. package/src/metadata-builder/builders/section-builders/move-section-builder.ts +35 -58
  57. package/src/metadata-builder/builders/section-field-maps.ts +0 -18
  58. package/src/metadata-builder/metadata-executor.ts +0 -4
  59. package/src/metadata-builder/models.ts +0 -2
  60. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +3 -19
  61. package/src/metadata-builder/parsers/section-tool-arguments.ts +0 -18
  62. package/src/utils/build-asset-manifest.ts +0 -12
  63. package/src/utils/minify-brand-page-metadata.ts +13 -5
  64. package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.d.ts +0 -29
  65. package/lib/src/brand-page-maker/composables/ai-chat/ui-actions.js +0 -32
  66. package/lib/src/metadata-builder/builders/move-page-builder.d.ts +0 -32
  67. package/lib/src/metadata-builder/builders/move-page-builder.js +0 -137
  68. package/lib/src/metadata-builder/builders/nav-utils.d.ts +0 -26
  69. package/lib/src/metadata-builder/builders/nav-utils.js +0 -95
  70. package/lib/src/metadata-builder/builders/set-page-visibility-builder.d.ts +0 -23
  71. package/lib/src/metadata-builder/builders/set-page-visibility-builder.js +0 -83
  72. package/src/brand-page-maker/composables/ai-chat/ui-actions.test.ts +0 -80
  73. package/src/brand-page-maker/composables/ai-chat/ui-actions.ts +0 -58
  74. package/src/metadata-builder/builders/move-page-builder.ts +0 -227
  75. package/src/metadata-builder/builders/nav-utils.ts +0 -141
  76. package/src/metadata-builder/builders/set-page-visibility-builder.ts +0 -122
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "275",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "251",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "250",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as e, createElementBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-DfeZjykC.js";
2
+ import { _ as t } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-DfeZjykC.js";
2
+ import { _ as e } from "./lib-yEV1yYI5.js";
3
3
  const M = {}, Z = {
4
4
  width: "242",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-DfeZjykC.js";
2
+ import { _ as e } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, M = {
4
4
  width: "231",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-DfeZjykC.js";
2
+ import { _ as e } from "./lib-yEV1yYI5.js";
3
3
  const M = {}, Z = {
4
4
  width: "230",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-DfeZjykC.js";
2
+ import { _ as e } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-DfeZjykC.js";
2
+ import { _ as e } from "./lib-yEV1yYI5.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-DfeZjykC.js";
2
+ import { _ as M } from "./lib-yEV1yYI5.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { openBlock as H, createElementBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-DfeZjykC.js";
2
+ import { _ as M } from "./lib-yEV1yYI5.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -48,9 +48,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
48
48
  }>> & Readonly<{
49
49
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
50
50
  }>, {
51
+ modelValue: number;
51
52
  min: number;
52
53
  max: number;
53
- modelValue: number;
54
54
  axis: string;
55
55
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
56
56
  export default _default;
@@ -1,7 +1,5 @@
1
1
  import type { ComputedRef, Ref } from 'vue';
2
2
  import { type PlanItem } from '../../../clients/websocket-gateway-client';
3
- import type { AgentAction } from './ui-actions';
4
- export type { AgentAction };
5
3
  export interface BaseThreadMessage {
6
4
  id: string;
7
5
  role: 'user' | 'agent';
@@ -16,7 +14,6 @@ export interface TextThreadMessage extends BaseThreadMessage {
16
14
  initialMessage?: string;
17
15
  planItems?: PlanItem[];
18
16
  completedItemIds?: string[];
19
- actions?: AgentAction[];
20
17
  }
21
18
  export interface ImageThreadMessage extends BaseThreadMessage {
22
19
  type: 'image';
@@ -6,7 +6,6 @@ import { AI_ASSISTANT_TOOL_NAMES } from '../../../metadata-builder/parsers/ai-to
6
6
  import { WebSocketGatewayClient, } from '../../../clients/websocket-gateway-client';
7
7
  import { minifyBrandPageMetadata } from '../../../utils/minify-brand-page-metadata';
8
8
  import { buildAssetManifest } from '../../../utils/build-asset-manifest';
9
- import cloneDeep from 'lodash.clonedeep';
10
9
  import { usePageNavigationStore } from '../../../shared/page-navigation/store/page-navigation-store';
11
10
  import { navigateToPageByPageId } from '../../routing';
12
11
  import { useSnapshotStore, useAiWebsiteBuilderThreadStore, useAiCheckpointStore, useBrandPageContainerStore, useUiStore, } from '../../stores';
@@ -19,7 +18,6 @@ import { pickErrorMessage, pickWsErrorMessage, buildSubmitErrorReport, } from '.
19
18
  import raygunUtil from '../../../brand-page/utils/raygun.util';
20
19
  import { useEventTracker, AI_CTA_LOCATIONS } from '../useEventTracker';
21
20
  import { Logger } from '../../../metadata-builder/logger';
22
- import { partitionAiToolCalls } from './ui-actions';
23
21
  const BUILD_TIMEOUT_MS = 600000;
24
22
  const aiLogger = new Logger();
25
23
  let activeBuildWsClient = null;
@@ -189,7 +187,6 @@ export function useAiWebsiteBuilderChat() {
189
187
  const currentPageId = pageNavigationStore.activePageId;
190
188
  const websiteContext = {
191
189
  metadata: minifyBrandPageMetadata(brandPageData, assets),
192
- metadataFull: cloneDeep(brandPageData),
193
190
  ...(assets.length > 0 && { assets }),
194
191
  websiteTemplateToken: containerStore.designTemplateToken,
195
192
  ...(containerStore.designDraftToken && {
@@ -373,7 +370,6 @@ export function useAiWebsiteBuilderChat() {
373
370
  let settled = false;
374
371
  let retryCount = 0;
375
372
  let lastAiMessage = null;
376
- let lastActions;
377
373
  let handler = null;
378
374
  // Vacant-slot queue scoped to this build session. Persists across
379
375
  // retry rounds so a failed CREATE returns to its planned slot via
@@ -393,173 +389,6 @@ export function useAiWebsiteBuilderChat() {
393
389
  finishBuild();
394
390
  reject(new Error('Build timeout'));
395
391
  }, BUILD_TIMEOUT_MS);
396
- // Transition the build to "done" exactly once (idempotent). Returns
397
- // false when already settled, so callers can early-return.
398
- const markDone = () => {
399
- if (settled)
400
- return false;
401
- settled = true;
402
- clearTimeout(timeout);
403
- finishBuild();
404
- return true;
405
- };
406
- // The single terminal path. Every resolve goes through here, so "where
407
- // does this Promise settle?" has one answer.
408
- const settle = (result) => {
409
- if (markDone())
410
- resolve(result);
411
- };
412
- // Streaming progress frames mutate the in-flight typing bubble and never
413
- // settle the build. Returns true when `msg` was a progress frame.
414
- const applyProgressUpdate = (msg) => {
415
- if (msg.type === 'chat_started' && msg.message) {
416
- withTypingMessage((m) => {
417
- m.initialMessage = msg.message;
418
- });
419
- return true;
420
- }
421
- if (msg.type === 'plan_announced' && msg.items) {
422
- withTypingMessage((m) => {
423
- if (!m.planItems)
424
- m.planItems = msg.items;
425
- });
426
- return true;
427
- }
428
- if (msg.type === 'item_complete' && msg.itemId) {
429
- const itemId = msg.itemId;
430
- withTypingMessage((m) => {
431
- if (!m.completedItemIds?.includes(itemId)) {
432
- m.completedItemIds = [...(m.completedItemIds ?? []), itemId];
433
- }
434
- });
435
- return true;
436
- }
437
- return false;
438
- };
439
- // Apply one round of build tool calls: settle on completion/failure, or
440
- // bump retryCount so the handler processes the next round's frame.
441
- const processServerResponse = async (aiResponse) => {
442
- const toolCalls = aiResponse.toolCalls ?? [];
443
- try {
444
- // No tool calls = build cycle complete (success, max retries, or no retryable sections).
445
- if (toolCalls.length === 0) {
446
- // Defence-in-depth: empty toolCalls AND empty message is a
447
- // degenerate response (graph reached END producing nothing). Never
448
- // show "Your website has been updated" — it hides real failures.
449
- const hasMeaningfulMessage = Boolean(aiResponse.message || lastAiMessage);
450
- if (!hasMeaningfulMessage) {
451
- raygunUtil.safeSendRaygunError(new Error('AI build server_response had empty toolCalls and empty message'), {
452
- tags: [
453
- 'feature:ai-website-builder',
454
- 'failure:degenerate_server_response',
455
- ],
456
- customData: {
457
- build_id: aiThreadStore.buildId ?? null,
458
- thread_id: aiThreadStore.threadId ?? null,
459
- },
460
- });
461
- settle({
462
- text: 'Something went wrong while updating your website. Please try again.',
463
- isError: true,
464
- });
465
- return;
466
- }
467
- // Mark settled BEFORE the await so concurrent frames are ignored.
468
- if (!markDone())
469
- return;
470
- await snapshotStore.refreshSnapshotAsync();
471
- resolve({
472
- text: aiResponse.message || lastAiMessage || '',
473
- actions: lastActions,
474
- });
475
- return;
476
- }
477
- // Tool calls present — apply locally and submit results back.
478
- const { toolResults, aiMessage } = await applyToolCallsAsync(aiResponse, userMessageId, vacantSlots);
479
- lastAiMessage = aiMessage;
480
- // Submit all tool results (success + failure) so the AI pipeline
481
- // persists ToolMessages matching every tool_call in the conversation.
482
- const failedResults = toolResults.filter((tr) => tr.success !== 'success');
483
- aiLogger.log('tool submission for results', {
484
- total: toolResults.length,
485
- failed: failedResults.length,
486
- failedResults,
487
- retryCount,
488
- });
489
- const submitResult = await client.submitToolResultsBuildAsync(aiThreadStore.threadId, {
490
- toolCalls: toolResults,
491
- agentType: AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
492
- connectionId,
493
- retryCount,
494
- userPlan,
495
- });
496
- if (failedResults.length === 0) {
497
- settle({
498
- text: aiMessage || 'Your website has been updated.',
499
- actions: lastActions,
500
- });
501
- return;
502
- }
503
- // Failed sections will retry: refresh buildId so the handler matches
504
- // the next round's frames, and count the round.
505
- if (submitResult.buildId) {
506
- aiThreadStore.buildId = submitResult.buildId;
507
- }
508
- retryCount += 1;
509
- }
510
- catch (err) {
511
- aiLogger.log('server_response apply error', { err });
512
- // Client-side apply failure (metadata builder, tool-call parser,
513
- // missing field, etc.) — the server thinks the build succeeded but we
514
- // couldn't materialise the changes. Surface it and report to Raygun
515
- // (the server didn't see this failure).
516
- raygunUtil.safeSendRaygunError(err instanceof Error
517
- ? err
518
- : new Error('AI apply failed: non-Error thrown'), {
519
- tags: [
520
- 'feature:ai-website-builder',
521
- 'failure:apply_error',
522
- 'stage:client_apply',
523
- ],
524
- customData: {
525
- thread_id: aiThreadStore.threadId ?? null,
526
- build_id: aiThreadStore.buildId ?? null,
527
- },
528
- });
529
- settle({
530
- text: "I couldn't apply those changes to your website. Please try again.",
531
- isError: true,
532
- });
533
- }
534
- };
535
- const handleBuildError = (msg) => {
536
- if (!markDone())
537
- return;
538
- // Backend already reports the underlying pipeline failure to Raygun
539
- // (with full traceback). We report the frontend's RECEIPT of the error
540
- // frame as a separate signal — distinct tag/grouping — for triangulation:
541
- // both sides → normal crash; frontend only → backend Raygun outage;
542
- // backend only → WS frame didn't reach client.
543
- const wsMsg = msg;
544
- raygunUtil.safeSendRaygunError(new Error(`AI build ${wsMsg.type}: ${wsMsg.code ?? wsMsg.error ?? 'unknown'}`), {
545
- tags: [
546
- 'feature:ai-website-builder',
547
- 'failure:websocket_error_received',
548
- `frame_type:${wsMsg.type}`,
549
- ],
550
- customData: {
551
- code: wsMsg.code ?? null,
552
- message: wsMsg.message ?? null,
553
- error: wsMsg.error ?? null,
554
- build_id: wsMsg.buildId ?? null,
555
- thread_id: aiThreadStore.threadId ?? null,
556
- },
557
- });
558
- resolve({ text: pickWsErrorMessage(msg), isError: true });
559
- };
560
- // Single dispatch point: ignore stale/already-settled frames, then route
561
- // by frame kind. Progress frames update the bubble; server_response and
562
- // error frames are the only ones that can settle the build.
563
392
  handler = (msg) => {
564
393
  if (settled)
565
394
  return;
@@ -567,32 +396,183 @@ export function useAiWebsiteBuilderChat() {
567
396
  if (msg.buildId !== aiThreadStore.buildId)
568
397
  return;
569
398
  if (isStopped.value) {
570
- settle({ text: null });
399
+ settled = true;
400
+ clearTimeout(timeout);
401
+ finishBuild();
402
+ resolve({ text: null });
403
+ return;
404
+ }
405
+ if (msg.type === 'chat_started' && msg.message) {
406
+ withTypingMessage((m) => {
407
+ m.initialMessage = msg.message;
408
+ });
409
+ return;
410
+ }
411
+ if (msg.type === 'plan_announced' && msg.items) {
412
+ withTypingMessage((m) => {
413
+ if (!m.planItems)
414
+ m.planItems = msg.items;
415
+ });
571
416
  return;
572
417
  }
573
- if (applyProgressUpdate(msg))
418
+ if (msg.type === 'item_complete' && msg.itemId) {
419
+ const itemId = msg.itemId;
420
+ withTypingMessage((m) => {
421
+ if (!m.completedItemIds?.includes(itemId)) {
422
+ m.completedItemIds = [
423
+ ...(m.completedItemIds ?? []),
424
+ itemId,
425
+ ];
426
+ }
427
+ });
574
428
  return;
429
+ }
575
430
  if (msg.type === 'server_response') {
576
- // CTA buttons (e.g. Upgrade) ride in toolCalls but are presentation,
577
- // not page edits. Split them out so only real mutations reach the
578
- // metadata-builder and the tool-result submission — a UI action must
579
- // never be answered with a tool result (it isn't in the agent's
580
- // conversation), or the next turn on the thread fails.
581
- const { mutationToolCalls, actions } = partitionAiToolCalls((msg.toolCalls || []));
582
- if (actions.length)
583
- lastActions = actions;
584
- void processServerResponse({
431
+ const toolCalls = (msg.toolCalls ||
432
+ []);
433
+ const aiResponse = {
585
434
  threadId: aiThreadStore.threadId || '',
586
- toolCalls: mutationToolCalls,
435
+ toolCalls,
587
436
  message: msg.message || null,
588
437
  type: 'tool_calls',
589
438
  totalPaidToolCalls: 0,
590
439
  timestamp: Date.now(),
591
- });
440
+ };
441
+ (async () => {
442
+ try {
443
+ // No tool calls = build cycle complete (success, max retries, or no retryable sections)
444
+ if (toolCalls.length === 0) {
445
+ // Defence-in-depth: a server_response with no toolCalls AND
446
+ // no message text is a degenerate response — it means the
447
+ // backend graph reached END without producing anything
448
+ // actionable. The server-side fix in stream_with_push now
449
+ // converts this to a build_error frame, but if a future
450
+ // regression slips through we must NOT show "Your website
451
+ // has been updated" — that hides real failures from users
452
+ // and from monitoring.
453
+ const hasMeaningfulMessage = Boolean(aiResponse.message || lastAiMessage);
454
+ if (!hasMeaningfulMessage) {
455
+ raygunUtil.safeSendRaygunError(new Error('AI build server_response had empty toolCalls and empty message'), {
456
+ tags: [
457
+ 'feature:ai-website-builder',
458
+ 'failure:degenerate_server_response',
459
+ ],
460
+ customData: {
461
+ build_id: aiThreadStore.buildId ?? null,
462
+ thread_id: aiThreadStore.threadId ?? null,
463
+ },
464
+ });
465
+ settled = true;
466
+ clearTimeout(timeout);
467
+ finishBuild();
468
+ resolve({
469
+ text: 'Something went wrong while updating your website. Please try again.',
470
+ isError: true,
471
+ });
472
+ return;
473
+ }
474
+ settled = true;
475
+ clearTimeout(timeout);
476
+ finishBuild();
477
+ resolve({
478
+ text: aiResponse.message || lastAiMessage || '',
479
+ });
480
+ return;
481
+ }
482
+ // Tool calls present — apply locally and submit results back
483
+ const { toolResults, aiMessage } = await applyToolCallsAsync(aiResponse, userMessageId, vacantSlots);
484
+ lastAiMessage = aiMessage;
485
+ // Submit all tool results (success + failure) so the AI pipeline
486
+ // persists ToolMessages matching every tool_call in the conversation.
487
+ const failedResults = toolResults.filter((tr) => tr.success !== 'success');
488
+ aiLogger.log('tool submission for results', {
489
+ total: toolResults.length,
490
+ failed: failedResults.length,
491
+ failedResults,
492
+ retryCount,
493
+ });
494
+ const submitResult = await client.submitToolResultsBuildAsync(aiThreadStore.threadId, {
495
+ toolCalls: toolResults,
496
+ agentType: AI_ASSISTANT_AGENT_TYPES.WEBSITE_BUILDER,
497
+ connectionId,
498
+ retryCount,
499
+ userPlan,
500
+ });
501
+ if (failedResults.length === 0) {
502
+ settled = true;
503
+ clearTimeout(timeout);
504
+ finishBuild();
505
+ resolve({
506
+ text: aiMessage || 'Your website has been updated.',
507
+ });
508
+ return;
509
+ }
510
+ // Update buildId so the handler matches subsequent WS messages
511
+ if (submitResult.buildId) {
512
+ aiThreadStore.buildId = submitResult.buildId;
513
+ }
514
+ // eslint-disable-next-line no-plusplus
515
+ retryCount++;
516
+ }
517
+ catch (err) {
518
+ aiLogger.log('server_response apply error', { err });
519
+ // Client-side apply failure (metadata builder, tool-call parser,
520
+ // missing field, etc.) — server thinks the build succeeded but we
521
+ // couldn't materialise the changes. Mark the chat bubble as an
522
+ // error so the user sees the real outcome, and report to Raygun
523
+ // so the failure is visible in monitoring (the server didn't see it).
524
+ raygunUtil.safeSendRaygunError(err instanceof Error
525
+ ? err
526
+ : new Error('AI apply failed: non-Error thrown'), {
527
+ tags: [
528
+ 'feature:ai-website-builder',
529
+ 'failure:apply_error',
530
+ 'stage:client_apply',
531
+ ],
532
+ customData: {
533
+ thread_id: aiThreadStore.threadId ?? null,
534
+ build_id: aiThreadStore.buildId ?? null,
535
+ },
536
+ });
537
+ settled = true;
538
+ clearTimeout(timeout);
539
+ finishBuild();
540
+ resolve({
541
+ text: "I couldn't apply those changes to your website. Please try again.",
542
+ isError: true,
543
+ });
544
+ }
545
+ })();
592
546
  return;
593
547
  }
594
548
  if (msg.type === 'server_error' || msg.type === 'build_error') {
595
- handleBuildError(msg);
549
+ settled = true;
550
+ clearTimeout(timeout);
551
+ finishBuild();
552
+ // Backend already reports the underlying pipeline failure to
553
+ // Raygun via its structured handler (stage:run_build /
554
+ // stage:section_builder / etc. with full Python traceback).
555
+ // We report the frontend's RECEIPT of the error frame as a
556
+ // separate signal — distinct tag, distinct grouping. This
557
+ // gives triangulation: both sides have entry → normal crash;
558
+ // frontend only → backend Raygun outage; backend only → WS
559
+ // frame didn't reach client (different bug shape).
560
+ const wsMsg = msg;
561
+ raygunUtil.safeSendRaygunError(new Error(`AI build ${wsMsg.type}: ${wsMsg.code ?? wsMsg.error ?? 'unknown'}`), {
562
+ tags: [
563
+ 'feature:ai-website-builder',
564
+ 'failure:websocket_error_received',
565
+ `frame_type:${wsMsg.type}`,
566
+ ],
567
+ customData: {
568
+ code: wsMsg.code ?? null,
569
+ message: wsMsg.message ?? null,
570
+ error: wsMsg.error ?? null,
571
+ build_id: wsMsg.buildId ?? null,
572
+ thread_id: aiThreadStore.threadId ?? null,
573
+ },
574
+ });
575
+ resolve({ text: pickWsErrorMessage(msg), isError: true });
596
576
  return;
597
577
  }
598
578
  }
@@ -601,10 +581,12 @@ export function useAiWebsiteBuilderChat() {
601
581
  err,
602
582
  type: msg.type,
603
583
  });
604
- settle({
605
- text: 'An error occurred during the build.',
606
- isError: true,
607
- });
584
+ if (!settled) {
585
+ settled = true;
586
+ clearTimeout(timeout);
587
+ finishBuild();
588
+ resolve({ text: 'An error occurred during the build.' });
589
+ }
608
590
  }
609
591
  };
610
592
  wsClient.onMessage(handler);
@@ -690,15 +672,6 @@ export function useAiWebsiteBuilderChat() {
690
672
  if (result.isError) {
691
673
  threadItems.value[messageIndex].isError = true;
692
674
  }
693
- // Attach agent-driven actions (e.g. upgrade CTA) only on successful
694
- // (non-error) responses. Suppressed for premium users client-side as
695
- // a defence-in-depth guard (server also vetoes via eligibility logic).
696
- if (!result.isError &&
697
- result.actions?.length &&
698
- !userStore.hasPremiumSubscription) {
699
- threadItems.value[messageIndex].actions =
700
- result.actions;
701
- }
702
675
  }
703
676
  else {
704
677
  threadItems.value[messageIndex].text =
@@ -785,11 +758,6 @@ export function useAiWebsiteBuilderChat() {
785
758
  planItems: msg.planItems ?? [],
786
759
  completedItemIds: msg.completedItemIds ?? [],
787
760
  }),
788
- ...(msg.type === 'text' &&
789
- !msg.isTyping &&
790
- msg.actions?.length && {
791
- actions: msg.actions,
792
- }),
793
761
  },
794
762
  };
795
763
  });
@@ -85,7 +85,6 @@ export declare const UPGRADE_CTA_LOCATIONS: {
85
85
  };
86
86
  export declare const AI_CTA_LOCATIONS: {
87
87
  AI_BUILDER_TAB: string;
88
- AI_BUILDER_CHAT_UPSELL: string;
89
88
  };
90
89
  export declare const TRIGGER_TYPE: {
91
90
  AUTOMATIC: string;
@@ -66,7 +66,6 @@ export const UPGRADE_CTA_LOCATIONS = {
66
66
  };
67
67
  export const AI_CTA_LOCATIONS = {
68
68
  AI_BUILDER_TAB: 'ai_builder_tab',
69
- AI_BUILDER_CHAT_UPSELL: 'ai_builder_chat_upsell',
70
69
  };
71
70
  export const TRIGGER_TYPE = {
72
71
  AUTOMATIC: 'automatic',
@@ -53,14 +53,14 @@ const PADDING_CONTROL_FIELDS = (groupId) => ({
53
53
  type: 'slider',
54
54
  title: contentSettingsTr(`topPaddingTitle`),
55
55
  minLength: 0,
56
- maxLength: 200,
56
+ maxLength: 100,
57
57
  groupId,
58
58
  },
59
59
  bottomPadding: {
60
60
  type: 'slider',
61
61
  title: contentSettingsTr(`bottomPaddingTitle`),
62
62
  minLength: 0,
63
- maxLength: 200,
63
+ maxLength: 100,
64
64
  groupId,
65
65
  },
66
66
  });
@@ -1,6 +1,5 @@
1
1
  import type { MinifiedBrandPageMetadata } from '../utils/minify-brand-page-metadata';
2
2
  import type { AssetEntry } from '../utils/build-asset-manifest';
3
- import type { BrandPageModel } from '../brand-page/models';
4
3
  export interface ActiveContext {
5
4
  currentPageId: string;
6
5
  }
@@ -10,12 +9,6 @@ export interface LogoContext {
10
9
  }
11
10
  export interface WebsiteContext {
12
11
  metadata: MinifiedBrandPageMetadata;
13
- /**
14
- * Full structural metadata (logos externalized to `logoAssetKey`, no base64,
15
- * no field-stripping) — persisted for the preview render only. The LLM uses
16
- * `metadata` (minified); the preview renderer uses this for full fidelity.
17
- */
18
- metadataFull?: BrandPageModel;
19
12
  assets?: AssetEntry[];
20
13
  activeContext?: ActiveContext;
21
14
  businessName?: string;