@puckeditor/plugin-ai 0.8.3-canary.70014e71 → 0.8.3-canary.8cefba87

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 (3) hide show
  1. package/dist/index.js +166 -30
  2. package/dist/index.mjs +166 -30
  3. package/package.json +11 -8
package/dist/index.js CHANGED
@@ -742,7 +742,7 @@ var import_ulid2 = require("ulid");
742
742
  // src/components/Chat/index.tsx
743
743
  init_react_import();
744
744
 
745
- // ../../node_modules/.pnpm/@ai-sdk+react@3.0.232_react@19.2.8_zod@4.4.3/node_modules/@ai-sdk/react/dist/index.mjs
745
+ // ../../node_modules/.pnpm/@ai-sdk+react@3.0.232_react@19.2.8_zod@3.25.76/node_modules/@ai-sdk/react/dist/index.mjs
746
746
  init_react_import();
747
747
  var import_ai = require("ai");
748
748
  var import_react4 = require("react");
@@ -3420,6 +3420,29 @@ var syncFieldValues = (entry, fields, props) => {
3420
3420
  });
3421
3421
  }
3422
3422
  };
3423
+ var claimedHostStyles = /* @__PURE__ */ new WeakMap();
3424
+ var claimHostStyles = (el, applyWhiteSpace) => {
3425
+ if (!claimedHostStyles.has(el)) {
3426
+ claimedHostStyles.set(el, {
3427
+ cursor: el.style.cursor,
3428
+ whiteSpace: applyWhiteSpace ? el.style.whiteSpace : null
3429
+ });
3430
+ }
3431
+ applyHostStyles(el);
3432
+ };
3433
+ var applyHostStyles = (el) => {
3434
+ const claimed = claimedHostStyles.get(el);
3435
+ if (!claimed) return;
3436
+ el.style.cursor = "text";
3437
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = "pre-wrap";
3438
+ };
3439
+ var releaseHostStyles = (el) => {
3440
+ const claimed = claimedHostStyles.get(el);
3441
+ if (!claimed) return;
3442
+ el.style.cursor = claimed.cursor;
3443
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = claimed.whiteSpace;
3444
+ claimedHostStyles.delete(el);
3445
+ };
3423
3446
  var attachInlineEditing = (el, {
3424
3447
  componentId,
3425
3448
  propPath,
@@ -3432,8 +3455,7 @@ var attachInlineEditing = (el, {
3432
3455
  const syncEditable = () => {
3433
3456
  el.contentEditable = hovering || focused ? mode === "richtext" ? "true" : "plaintext-only" : "false";
3434
3457
  };
3435
- const previousCursor = el.style.cursor;
3436
- el.style.cursor = "text";
3458
+ claimHostStyles(el, mode !== "richtext");
3437
3459
  syncEditable();
3438
3460
  let lastValidNumberText = el.innerText.replaceAll(/\n/gm, "");
3439
3461
  const handleInput = () => {
@@ -3511,7 +3533,7 @@ var attachInlineEditing = (el, {
3511
3533
  el.removeEventListener("mouseout", handleMouseOut, true);
3512
3534
  el.removeEventListener("focus", handleFocus);
3513
3535
  el.removeEventListener("blur", handleBlur);
3514
- el.style.cursor = previousCursor;
3536
+ releaseHostStyles(el);
3515
3537
  el.removeAttribute("contenteditable");
3516
3538
  cleanupPortal?.();
3517
3539
  };
@@ -3577,7 +3599,7 @@ var wireArrayItemEditing = ({
3577
3599
  const mode = inlineEditModeFor(field);
3578
3600
  const registration = wiring.get(target);
3579
3601
  if (registration?.propPath === propPath && registration.mode === mode) {
3580
- target.style.cursor = "text";
3602
+ applyHostStyles(target);
3581
3603
  continue;
3582
3604
  }
3583
3605
  registration?.cleanup();
@@ -3903,7 +3925,7 @@ function createDesignComponentConfig(registration) {
3903
3925
  morphed.current = { el: entryEl, html, attrSig };
3904
3926
  morphHtml(entryEl, html, attrFields);
3905
3927
  for (const el of fieldWiring.current.keys()) {
3906
- if (el.isConnected) el.style.cursor = "text";
3928
+ if (el.isConnected) applyHostStyles(el);
3907
3929
  }
3908
3930
  }
3909
3931
  syncFieldValues(entryEl, annotations.fields, props);
@@ -4062,9 +4084,11 @@ function getDynamicConfigGlobals(dynamicConfig) {
4062
4084
  ...typeof script === "string" ? { script } : {}
4063
4085
  };
4064
4086
  }
4087
+ var INLINE_EDIT_RESET = ":where([data-puck-design] [contenteditable]){overflow-wrap:inherit;line-break:inherit;-webkit-nbsp-mode:inherit}";
4065
4088
  function DesignGlobals({
4066
4089
  styles,
4067
- script
4090
+ script,
4091
+ isEditing
4068
4092
  }) {
4069
4093
  const scriptRef = (0, import_react25.useRef)(null);
4070
4094
  const hostRef = (0, import_react25.useRef)(null);
@@ -4082,6 +4106,7 @@ function DesignGlobals({
4082
4106
  };
4083
4107
  }, [script]);
4084
4108
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
4109
+ isEditing && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: INLINE_EDIT_RESET }),
4085
4110
  styles && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("style", { children: styles }),
4086
4111
  script && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { ref: hostRef, style: { display: "none" } })
4087
4112
  ] });
@@ -4094,7 +4119,14 @@ function withDesignGlobals(root) {
4094
4119
  const render = (props) => {
4095
4120
  const { styles, script } = getDynamicConfigGlobals(props._dynamicConfig);
4096
4121
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(import_jsx_runtime33.Fragment, { children: [
4097
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DesignGlobals, { styles, script }),
4122
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4123
+ DesignGlobals,
4124
+ {
4125
+ styles,
4126
+ script,
4127
+ isEditing: props.puck?.isEditing
4128
+ }
4129
+ ),
4098
4130
  userRender ? userRender(props) : props.children
4099
4131
  ] });
4100
4132
  };
@@ -4360,12 +4392,28 @@ var waitForIframeReady = async (root, imageTimeout = 1e4) => {
4360
4392
  await waitForNextPaint(document2);
4361
4393
  };
4362
4394
 
4395
+ // src/lib/client-tool-response.ts
4396
+ init_react_import();
4397
+ async function postClientToolResponse(host, body) {
4398
+ const response = await fetch(`${host.replace(/\/+$/, "")}/tool`, {
4399
+ method: "POST",
4400
+ headers: { "Content-Type": "application/json" },
4401
+ body: JSON.stringify(body)
4402
+ });
4403
+ if (!response.ok) {
4404
+ throw new Error(
4405
+ `Client tool endpoint returned ${response.status} ${response.statusText}`.trim()
4406
+ );
4407
+ }
4408
+ }
4409
+
4363
4410
  // src/components/Chat/index.tsx
4364
4411
  var import_jsx_runtime35 = require("react/jsx-runtime");
4365
4412
  var q = (0, import_qler.default)();
4366
4413
  var DEFAULT_API_VERSION = "v2";
4367
4414
  var BUILD_OP_DEBOUNCE_MS = 1e3 / 60;
4368
- var PLUGIN_AI_VERSION = true ? "0.8.3-canary.70014e71" : "unknown";
4415
+ var CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE = "Unable to verify the page data. Please enable the `/chat/tool` endpoint on the server and try again.";
4416
+ var PLUGIN_AI_VERSION = true ? "0.8.3-canary.8cefba87" : "unknown";
4369
4417
  var BENCHMARK = false;
4370
4418
  var prefixedUlid = (prefix = "") => `${prefix ? `${prefix}_` : ""}${(0, import_ulid.ulid)()}`;
4371
4419
  var getClassName18 = getClassNameFactory("Chat", styles_module_default);
@@ -4403,10 +4451,14 @@ function Chat2(props) {
4403
4451
  }, [puckData, getPuck]);
4404
4452
  const [error, setError] = (0, import_react27.useState)();
4405
4453
  const [userError, setUserError] = (0, import_react27.useState)();
4454
+ const clientToolEndpointUnavailableRef = (0, import_react27.useRef)(false);
4455
+ const stopChatRef = (0, import_react27.useRef)(null);
4406
4456
  const [composerAttachments, setComposerAttachments] = (0, import_react27.useState)([]);
4407
4457
  const removedAttachmentClientIdsRef = (0, import_react27.useRef)(/* @__PURE__ */ new Set());
4408
4458
  const uploadControllersRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
4409
4459
  const pendingBuildOpsRef = (0, import_react27.useRef)([]);
4460
+ const lastStreamActivityRef = (0, import_react27.useRef)(0);
4461
+ const hiddenSinceRef = (0, import_react27.useRef)(null);
4410
4462
  const [toolStatus, setToolStatus] = (0, import_react27.useState)({});
4411
4463
  const [subagentState, setSubagentState] = (0, import_react27.useState)({});
4412
4464
  const attachmentConfig = (0, import_react27.useMemo)(
@@ -4517,8 +4569,43 @@ function Chat2(props) {
4517
4569
  },
4518
4570
  [debouncedFlushBuildOps]
4519
4571
  );
4572
+ const handleClientToolEndpointUnavailable = (0, import_react27.useCallback)(() => {
4573
+ if (clientToolEndpointUnavailableRef.current) return;
4574
+ clientToolEndpointUnavailableRef.current = true;
4575
+ setError(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4576
+ console.error(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4577
+ setToolStatus((current) => {
4578
+ let changed = false;
4579
+ const next = {};
4580
+ for (const [id, toolState] of Object.entries(current)) {
4581
+ if (toolState.loading) {
4582
+ changed = true;
4583
+ next[id] = {
4584
+ ...toolState,
4585
+ loading: false,
4586
+ label: "Verification unavailable"
4587
+ };
4588
+ } else {
4589
+ next[id] = toolState;
4590
+ }
4591
+ }
4592
+ return changed ? next : current;
4593
+ });
4594
+ stopChatRef.current?.();
4595
+ }, []);
4596
+ const sendClientToolResponse = (0, import_react27.useCallback)(
4597
+ async (body) => {
4598
+ try {
4599
+ await postClientToolResponse(host, body);
4600
+ } catch {
4601
+ handleClientToolEndpointUnavailable();
4602
+ }
4603
+ },
4604
+ [handleClientToolEndpointUnavailable, host]
4605
+ );
4520
4606
  const processData = (0, import_react27.useCallback)(
4521
4607
  (dataPart) => {
4608
+ lastStreamActivityRef.current = Date.now();
4522
4609
  switch (dataPart.type) {
4523
4610
  case "data-new-chat-created": {
4524
4611
  localChatId.current = dataPart.data.chatId;
@@ -4549,13 +4636,9 @@ function Chat2(props) {
4549
4636
  debouncedFlushBuildOps.flush();
4550
4637
  q.wait().then(() => {
4551
4638
  const { appState } = getPuck();
4552
- return fetch(`${host}/tool`, {
4553
- method: "POST",
4554
- headers: { "Content-Type": "application/json" },
4555
- body: JSON.stringify({
4556
- id: requestId,
4557
- responses: [{ action, output: appState.data }]
4558
- })
4639
+ return sendClientToolResponse({
4640
+ id: requestId,
4641
+ responses: [{ action, output: appState.data }]
4559
4642
  });
4560
4643
  }).catch((e) => {
4561
4644
  console.error("Failed to respond to client request:", e);
@@ -4597,13 +4680,9 @@ function Chat2(props) {
4597
4680
  `Upload failed with status ${uploadResponse.status}`
4598
4681
  );
4599
4682
  }
4600
- return fetch(`${host}/tool`, {
4601
- method: "POST",
4602
- headers: { "Content-Type": "application/json" },
4603
- body: JSON.stringify({
4604
- id: requestId,
4605
- responses: [{ action, output: { sizeBytes: blob.size } }]
4606
- })
4683
+ return sendClientToolResponse({
4684
+ id: requestId,
4685
+ responses: [{ action, output: { sizeBytes: blob.size } }]
4607
4686
  });
4608
4687
  }).catch((e) => {
4609
4688
  console.error("Failed to respond to client request:", e);
@@ -4650,6 +4729,7 @@ function Chat2(props) {
4650
4729
  getPuck,
4651
4730
  puckDispatch,
4652
4731
  queueBuildOp,
4732
+ sendClientToolResponse,
4653
4733
  uploadScreenshot
4654
4734
  ]
4655
4735
  );
@@ -4719,12 +4799,14 @@ function Chat2(props) {
4719
4799
  if (BENCHMARK) {
4720
4800
  console.timeEnd("chat");
4721
4801
  }
4722
- setError(e.message);
4802
+ if (!clientToolEndpointUnavailableRef.current) {
4803
+ setError(e.message);
4804
+ }
4723
4805
  },
4724
4806
  onFinish: (options) => {
4725
4807
  debouncedFlushBuildOps.flush();
4726
4808
  q.wait().then(() => {
4727
- if (!options.isAbort) {
4809
+ if (!options.isAbort || clientToolEndpointUnavailableRef.current) {
4728
4810
  puckDispatch({
4729
4811
  type: "set",
4730
4812
  state: getPuck().appState,
@@ -4739,12 +4821,63 @@ function Chat2(props) {
4739
4821
  });
4740
4822
  }
4741
4823
  });
4742
- (0, import_react27.useEffect)(
4743
- () => () => {
4824
+ (0, import_react27.useEffect)(() => {
4825
+ stopChatRef.current = stop;
4826
+ return () => {
4827
+ stopChatRef.current = null;
4744
4828
  stop();
4745
- },
4746
- [stop]
4747
- );
4829
+ };
4830
+ }, [stop]);
4831
+ const statusRef = (0, import_react27.useRef)(status);
4832
+ (0, import_react27.useEffect)(() => {
4833
+ statusRef.current = status;
4834
+ }, [status]);
4835
+ (0, import_react27.useEffect)(() => {
4836
+ lastStreamActivityRef.current = Date.now();
4837
+ }, [messages]);
4838
+ (0, import_react27.useEffect)(() => {
4839
+ const FROZEN_HIDDEN_MS = 3 * 6e4;
4840
+ const RESUME_GRACE_MS = 12e3;
4841
+ const isStreaming = () => statusRef.current === "streaming" || statusRef.current === "submitted";
4842
+ let graceTimer;
4843
+ const recoverIfSevered = () => {
4844
+ if (!isStreaming()) return;
4845
+ if (Date.now() - lastStreamActivityRef.current < RESUME_GRACE_MS) return;
4846
+ stop();
4847
+ setToolStatus((current) => {
4848
+ let changed = false;
4849
+ const next = {};
4850
+ for (const [id, toolState] of Object.entries(current)) {
4851
+ if (toolState.loading) {
4852
+ changed = true;
4853
+ next[id] = { ...toolState, loading: false, label: "Interrupted" };
4854
+ } else {
4855
+ next[id] = toolState;
4856
+ }
4857
+ }
4858
+ return changed ? next : current;
4859
+ });
4860
+ };
4861
+ const onVisibilityChange = () => {
4862
+ if (document.hidden) {
4863
+ hiddenSinceRef.current = isStreaming() ? Date.now() : null;
4864
+ return;
4865
+ }
4866
+ const hiddenSince = hiddenSinceRef.current;
4867
+ hiddenSinceRef.current = null;
4868
+ if (graceTimer) clearTimeout(graceTimer);
4869
+ if (hiddenSince === null || Date.now() - hiddenSince < FROZEN_HIDDEN_MS) {
4870
+ return;
4871
+ }
4872
+ lastStreamActivityRef.current = 0;
4873
+ graceTimer = setTimeout(recoverIfSevered, RESUME_GRACE_MS);
4874
+ };
4875
+ document.addEventListener("visibilitychange", onVisibilityChange);
4876
+ return () => {
4877
+ document.removeEventListener("visibilitychange", onVisibilityChange);
4878
+ if (graceTimer) clearTimeout(graceTimer);
4879
+ };
4880
+ }, [stop]);
4748
4881
  const [forcedStatus, setForcedStatus] = (0, import_react27.useState)();
4749
4882
  const resolvedStatus = (0, import_react27.useMemo)(
4750
4883
  () => forcedStatus ?? status,
@@ -4795,6 +4928,8 @@ function Chat2(props) {
4795
4928
  if (!text && readyAttachments.length === 0) {
4796
4929
  return false;
4797
4930
  }
4931
+ clientToolEndpointUnavailableRef.current = false;
4932
+ setError("");
4798
4933
  setUserError("");
4799
4934
  if (chat?.onSubmit) {
4800
4935
  try {
@@ -5034,6 +5169,7 @@ function Chat2(props) {
5034
5169
  ),
5035
5170
  error,
5036
5171
  handleRetry: () => {
5172
+ clientToolEndpointUnavailableRef.current = false;
5037
5173
  setError("");
5038
5174
  regenerate();
5039
5175
  },
package/dist/index.mjs CHANGED
@@ -729,7 +729,7 @@ import { ulid as ulid2 } from "ulid";
729
729
  // src/components/Chat/index.tsx
730
730
  init_react_import();
731
731
 
732
- // ../../node_modules/.pnpm/@ai-sdk+react@3.0.232_react@19.2.8_zod@4.4.3/node_modules/@ai-sdk/react/dist/index.mjs
732
+ // ../../node_modules/.pnpm/@ai-sdk+react@3.0.232_react@19.2.8_zod@3.25.76/node_modules/@ai-sdk/react/dist/index.mjs
733
733
  init_react_import();
734
734
  var import_throttleit = __toESM(require_throttleit(), 1);
735
735
  import {
@@ -3452,6 +3452,29 @@ var syncFieldValues = (entry, fields, props) => {
3452
3452
  });
3453
3453
  }
3454
3454
  };
3455
+ var claimedHostStyles = /* @__PURE__ */ new WeakMap();
3456
+ var claimHostStyles = (el, applyWhiteSpace) => {
3457
+ if (!claimedHostStyles.has(el)) {
3458
+ claimedHostStyles.set(el, {
3459
+ cursor: el.style.cursor,
3460
+ whiteSpace: applyWhiteSpace ? el.style.whiteSpace : null
3461
+ });
3462
+ }
3463
+ applyHostStyles(el);
3464
+ };
3465
+ var applyHostStyles = (el) => {
3466
+ const claimed = claimedHostStyles.get(el);
3467
+ if (!claimed) return;
3468
+ el.style.cursor = "text";
3469
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = "pre-wrap";
3470
+ };
3471
+ var releaseHostStyles = (el) => {
3472
+ const claimed = claimedHostStyles.get(el);
3473
+ if (!claimed) return;
3474
+ el.style.cursor = claimed.cursor;
3475
+ if (claimed.whiteSpace !== null) el.style.whiteSpace = claimed.whiteSpace;
3476
+ claimedHostStyles.delete(el);
3477
+ };
3455
3478
  var attachInlineEditing = (el, {
3456
3479
  componentId,
3457
3480
  propPath,
@@ -3464,8 +3487,7 @@ var attachInlineEditing = (el, {
3464
3487
  const syncEditable = () => {
3465
3488
  el.contentEditable = hovering || focused ? mode === "richtext" ? "true" : "plaintext-only" : "false";
3466
3489
  };
3467
- const previousCursor = el.style.cursor;
3468
- el.style.cursor = "text";
3490
+ claimHostStyles(el, mode !== "richtext");
3469
3491
  syncEditable();
3470
3492
  let lastValidNumberText = el.innerText.replaceAll(/\n/gm, "");
3471
3493
  const handleInput = () => {
@@ -3543,7 +3565,7 @@ var attachInlineEditing = (el, {
3543
3565
  el.removeEventListener("mouseout", handleMouseOut, true);
3544
3566
  el.removeEventListener("focus", handleFocus);
3545
3567
  el.removeEventListener("blur", handleBlur);
3546
- el.style.cursor = previousCursor;
3568
+ releaseHostStyles(el);
3547
3569
  el.removeAttribute("contenteditable");
3548
3570
  cleanupPortal?.();
3549
3571
  };
@@ -3609,7 +3631,7 @@ var wireArrayItemEditing = ({
3609
3631
  const mode = inlineEditModeFor(field);
3610
3632
  const registration = wiring.get(target);
3611
3633
  if (registration?.propPath === propPath && registration.mode === mode) {
3612
- target.style.cursor = "text";
3634
+ applyHostStyles(target);
3613
3635
  continue;
3614
3636
  }
3615
3637
  registration?.cleanup();
@@ -3935,7 +3957,7 @@ function createDesignComponentConfig(registration) {
3935
3957
  morphed.current = { el: entryEl, html, attrSig };
3936
3958
  morphHtml(entryEl, html, attrFields);
3937
3959
  for (const el of fieldWiring.current.keys()) {
3938
- if (el.isConnected) el.style.cursor = "text";
3960
+ if (el.isConnected) applyHostStyles(el);
3939
3961
  }
3940
3962
  }
3941
3963
  syncFieldValues(entryEl, annotations.fields, props);
@@ -4094,9 +4116,11 @@ function getDynamicConfigGlobals(dynamicConfig) {
4094
4116
  ...typeof script === "string" ? { script } : {}
4095
4117
  };
4096
4118
  }
4119
+ var INLINE_EDIT_RESET = ":where([data-puck-design] [contenteditable]){overflow-wrap:inherit;line-break:inherit;-webkit-nbsp-mode:inherit}";
4097
4120
  function DesignGlobals({
4098
4121
  styles,
4099
- script
4122
+ script,
4123
+ isEditing
4100
4124
  }) {
4101
4125
  const scriptRef = useRef12(null);
4102
4126
  const hostRef = useRef12(null);
@@ -4114,6 +4138,7 @@ function DesignGlobals({
4114
4138
  };
4115
4139
  }, [script]);
4116
4140
  return /* @__PURE__ */ jsxs19(Fragment8, { children: [
4141
+ isEditing && /* @__PURE__ */ jsx33("style", { children: INLINE_EDIT_RESET }),
4117
4142
  styles && /* @__PURE__ */ jsx33("style", { children: styles }),
4118
4143
  script && /* @__PURE__ */ jsx33("div", { ref: hostRef, style: { display: "none" } })
4119
4144
  ] });
@@ -4126,7 +4151,14 @@ function withDesignGlobals(root) {
4126
4151
  const render = (props) => {
4127
4152
  const { styles, script } = getDynamicConfigGlobals(props._dynamicConfig);
4128
4153
  return /* @__PURE__ */ jsxs19(Fragment8, { children: [
4129
- /* @__PURE__ */ jsx33(DesignGlobals, { styles, script }),
4154
+ /* @__PURE__ */ jsx33(
4155
+ DesignGlobals,
4156
+ {
4157
+ styles,
4158
+ script,
4159
+ isEditing: props.puck?.isEditing
4160
+ }
4161
+ ),
4130
4162
  userRender ? userRender(props) : props.children
4131
4163
  ] });
4132
4164
  };
@@ -4392,12 +4424,28 @@ var waitForIframeReady = async (root, imageTimeout = 1e4) => {
4392
4424
  await waitForNextPaint(document2);
4393
4425
  };
4394
4426
 
4427
+ // src/lib/client-tool-response.ts
4428
+ init_react_import();
4429
+ async function postClientToolResponse(host, body) {
4430
+ const response = await fetch(`${host.replace(/\/+$/, "")}/tool`, {
4431
+ method: "POST",
4432
+ headers: { "Content-Type": "application/json" },
4433
+ body: JSON.stringify(body)
4434
+ });
4435
+ if (!response.ok) {
4436
+ throw new Error(
4437
+ `Client tool endpoint returned ${response.status} ${response.statusText}`.trim()
4438
+ );
4439
+ }
4440
+ }
4441
+
4395
4442
  // src/components/Chat/index.tsx
4396
4443
  import { Fragment as Fragment9, jsx as jsx35, jsxs as jsxs20 } from "react/jsx-runtime";
4397
4444
  var q = qler();
4398
4445
  var DEFAULT_API_VERSION = "v2";
4399
4446
  var BUILD_OP_DEBOUNCE_MS = 1e3 / 60;
4400
- var PLUGIN_AI_VERSION = true ? "0.8.3-canary.70014e71" : "unknown";
4447
+ var CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE = "Unable to verify the page data. Please enable the `/chat/tool` endpoint on the server and try again.";
4448
+ var PLUGIN_AI_VERSION = true ? "0.8.3-canary.8cefba87" : "unknown";
4401
4449
  var BENCHMARK = false;
4402
4450
  var prefixedUlid = (prefix = "") => `${prefix ? `${prefix}_` : ""}${ulid()}`;
4403
4451
  var getClassName18 = getClassNameFactory("Chat", styles_module_default);
@@ -4435,10 +4483,14 @@ function Chat2(props) {
4435
4483
  }, [puckData, getPuck]);
4436
4484
  const [error, setError] = useState16();
4437
4485
  const [userError, setUserError] = useState16();
4486
+ const clientToolEndpointUnavailableRef = useRef13(false);
4487
+ const stopChatRef = useRef13(null);
4438
4488
  const [composerAttachments, setComposerAttachments] = useState16([]);
4439
4489
  const removedAttachmentClientIdsRef = useRef13(/* @__PURE__ */ new Set());
4440
4490
  const uploadControllersRef = useRef13(/* @__PURE__ */ new Map());
4441
4491
  const pendingBuildOpsRef = useRef13([]);
4492
+ const lastStreamActivityRef = useRef13(0);
4493
+ const hiddenSinceRef = useRef13(null);
4442
4494
  const [toolStatus, setToolStatus] = useState16({});
4443
4495
  const [subagentState, setSubagentState] = useState16({});
4444
4496
  const attachmentConfig = useMemo6(
@@ -4549,8 +4601,43 @@ function Chat2(props) {
4549
4601
  },
4550
4602
  [debouncedFlushBuildOps]
4551
4603
  );
4604
+ const handleClientToolEndpointUnavailable = useCallback6(() => {
4605
+ if (clientToolEndpointUnavailableRef.current) return;
4606
+ clientToolEndpointUnavailableRef.current = true;
4607
+ setError(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4608
+ console.error(CLIENT_TOOL_ENDPOINT_UNAVAILABLE_ERROR_MESSAGE);
4609
+ setToolStatus((current) => {
4610
+ let changed = false;
4611
+ const next = {};
4612
+ for (const [id, toolState] of Object.entries(current)) {
4613
+ if (toolState.loading) {
4614
+ changed = true;
4615
+ next[id] = {
4616
+ ...toolState,
4617
+ loading: false,
4618
+ label: "Verification unavailable"
4619
+ };
4620
+ } else {
4621
+ next[id] = toolState;
4622
+ }
4623
+ }
4624
+ return changed ? next : current;
4625
+ });
4626
+ stopChatRef.current?.();
4627
+ }, []);
4628
+ const sendClientToolResponse = useCallback6(
4629
+ async (body) => {
4630
+ try {
4631
+ await postClientToolResponse(host, body);
4632
+ } catch {
4633
+ handleClientToolEndpointUnavailable();
4634
+ }
4635
+ },
4636
+ [handleClientToolEndpointUnavailable, host]
4637
+ );
4552
4638
  const processData = useCallback6(
4553
4639
  (dataPart) => {
4640
+ lastStreamActivityRef.current = Date.now();
4554
4641
  switch (dataPart.type) {
4555
4642
  case "data-new-chat-created": {
4556
4643
  localChatId.current = dataPart.data.chatId;
@@ -4581,13 +4668,9 @@ function Chat2(props) {
4581
4668
  debouncedFlushBuildOps.flush();
4582
4669
  q.wait().then(() => {
4583
4670
  const { appState } = getPuck();
4584
- return fetch(`${host}/tool`, {
4585
- method: "POST",
4586
- headers: { "Content-Type": "application/json" },
4587
- body: JSON.stringify({
4588
- id: requestId,
4589
- responses: [{ action, output: appState.data }]
4590
- })
4671
+ return sendClientToolResponse({
4672
+ id: requestId,
4673
+ responses: [{ action, output: appState.data }]
4591
4674
  });
4592
4675
  }).catch((e) => {
4593
4676
  console.error("Failed to respond to client request:", e);
@@ -4629,13 +4712,9 @@ function Chat2(props) {
4629
4712
  `Upload failed with status ${uploadResponse.status}`
4630
4713
  );
4631
4714
  }
4632
- return fetch(`${host}/tool`, {
4633
- method: "POST",
4634
- headers: { "Content-Type": "application/json" },
4635
- body: JSON.stringify({
4636
- id: requestId,
4637
- responses: [{ action, output: { sizeBytes: blob.size } }]
4638
- })
4715
+ return sendClientToolResponse({
4716
+ id: requestId,
4717
+ responses: [{ action, output: { sizeBytes: blob.size } }]
4639
4718
  });
4640
4719
  }).catch((e) => {
4641
4720
  console.error("Failed to respond to client request:", e);
@@ -4682,6 +4761,7 @@ function Chat2(props) {
4682
4761
  getPuck,
4683
4762
  puckDispatch,
4684
4763
  queueBuildOp,
4764
+ sendClientToolResponse,
4685
4765
  uploadScreenshot
4686
4766
  ]
4687
4767
  );
@@ -4751,12 +4831,14 @@ function Chat2(props) {
4751
4831
  if (BENCHMARK) {
4752
4832
  console.timeEnd("chat");
4753
4833
  }
4754
- setError(e.message);
4834
+ if (!clientToolEndpointUnavailableRef.current) {
4835
+ setError(e.message);
4836
+ }
4755
4837
  },
4756
4838
  onFinish: (options) => {
4757
4839
  debouncedFlushBuildOps.flush();
4758
4840
  q.wait().then(() => {
4759
- if (!options.isAbort) {
4841
+ if (!options.isAbort || clientToolEndpointUnavailableRef.current) {
4760
4842
  puckDispatch({
4761
4843
  type: "set",
4762
4844
  state: getPuck().appState,
@@ -4771,12 +4853,63 @@ function Chat2(props) {
4771
4853
  });
4772
4854
  }
4773
4855
  });
4774
- useEffect13(
4775
- () => () => {
4856
+ useEffect13(() => {
4857
+ stopChatRef.current = stop;
4858
+ return () => {
4859
+ stopChatRef.current = null;
4776
4860
  stop();
4777
- },
4778
- [stop]
4779
- );
4861
+ };
4862
+ }, [stop]);
4863
+ const statusRef = useRef13(status);
4864
+ useEffect13(() => {
4865
+ statusRef.current = status;
4866
+ }, [status]);
4867
+ useEffect13(() => {
4868
+ lastStreamActivityRef.current = Date.now();
4869
+ }, [messages]);
4870
+ useEffect13(() => {
4871
+ const FROZEN_HIDDEN_MS = 3 * 6e4;
4872
+ const RESUME_GRACE_MS = 12e3;
4873
+ const isStreaming = () => statusRef.current === "streaming" || statusRef.current === "submitted";
4874
+ let graceTimer;
4875
+ const recoverIfSevered = () => {
4876
+ if (!isStreaming()) return;
4877
+ if (Date.now() - lastStreamActivityRef.current < RESUME_GRACE_MS) return;
4878
+ stop();
4879
+ setToolStatus((current) => {
4880
+ let changed = false;
4881
+ const next = {};
4882
+ for (const [id, toolState] of Object.entries(current)) {
4883
+ if (toolState.loading) {
4884
+ changed = true;
4885
+ next[id] = { ...toolState, loading: false, label: "Interrupted" };
4886
+ } else {
4887
+ next[id] = toolState;
4888
+ }
4889
+ }
4890
+ return changed ? next : current;
4891
+ });
4892
+ };
4893
+ const onVisibilityChange = () => {
4894
+ if (document.hidden) {
4895
+ hiddenSinceRef.current = isStreaming() ? Date.now() : null;
4896
+ return;
4897
+ }
4898
+ const hiddenSince = hiddenSinceRef.current;
4899
+ hiddenSinceRef.current = null;
4900
+ if (graceTimer) clearTimeout(graceTimer);
4901
+ if (hiddenSince === null || Date.now() - hiddenSince < FROZEN_HIDDEN_MS) {
4902
+ return;
4903
+ }
4904
+ lastStreamActivityRef.current = 0;
4905
+ graceTimer = setTimeout(recoverIfSevered, RESUME_GRACE_MS);
4906
+ };
4907
+ document.addEventListener("visibilitychange", onVisibilityChange);
4908
+ return () => {
4909
+ document.removeEventListener("visibilitychange", onVisibilityChange);
4910
+ if (graceTimer) clearTimeout(graceTimer);
4911
+ };
4912
+ }, [stop]);
4780
4913
  const [forcedStatus, setForcedStatus] = useState16();
4781
4914
  const resolvedStatus = useMemo6(
4782
4915
  () => forcedStatus ?? status,
@@ -4827,6 +4960,8 @@ function Chat2(props) {
4827
4960
  if (!text && readyAttachments.length === 0) {
4828
4961
  return false;
4829
4962
  }
4963
+ clientToolEndpointUnavailableRef.current = false;
4964
+ setError("");
4830
4965
  setUserError("");
4831
4966
  if (chat?.onSubmit) {
4832
4967
  try {
@@ -5066,6 +5201,7 @@ function Chat2(props) {
5066
5201
  ),
5067
5202
  error,
5068
5203
  handleRetry: () => {
5204
+ clientToolEndpointUnavailableRef.current = false;
5069
5205
  setError("");
5070
5206
  regenerate();
5071
5207
  },
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@puckeditor/plugin-ai",
3
- "version": "0.8.3-canary.70014e71",
3
+ "version": "0.8.3-canary.8cefba87",
4
4
  "author": "Chris Villa <chris@puckeditor.com>",
5
- "repository": "puckeditor/puck",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "puckeditor/puck"
8
+ },
6
9
  "bugs": "https://github.com/puckeditor/puck/issues",
7
10
  "homepage": "https://puckeditor.com",
8
11
  "keywords": [
@@ -49,12 +52,12 @@
49
52
  "tsup": "^8.2.4",
50
53
  "typescript": "^5.5.4",
51
54
  "vitest": "^4.1.0",
52
- "@puckeditor/ai-types": "0.8.3-canary.70014e71",
53
- "@puckeditor/platform-types": "0.8.3-canary.70014e71",
54
- "eslint-config-custom": "0.8.3-canary.70014e71",
55
- "reducer": "0.8.3-canary.70014e71",
56
- "tsconfig": "0.8.3-canary.70014e71",
57
- "tsup-config": "0.8.3-canary.70014e71"
55
+ "@puckeditor/ai-types": "0.8.3-canary.8cefba87",
56
+ "@puckeditor/platform-types": "0.8.3-canary.8cefba87",
57
+ "eslint-config-custom": "0.8.3-canary.8cefba87",
58
+ "reducer": "0.8.3-canary.8cefba87",
59
+ "tsconfig": "0.8.3-canary.8cefba87",
60
+ "tsup-config": "0.8.3-canary.8cefba87"
58
61
  },
59
62
  "peerDependencies": {
60
63
  "@puckeditor/core": "^0",