@helpai/elements 0.61.4 → 0.62.0

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-D9QqhMzi.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, H as HandshakeResponse, L as Link, S as ServerConfig, b as SiteConfig, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial } from './deployment-i9cta98X.js';
2
2
  import 'zod';
3
3
 
4
4
  /**
@@ -871,6 +871,8 @@ interface BehaviorOptions {
871
871
  showSources?: boolean;
872
872
  /** Turn OFF the 👍/👎 feedback controls on assistant replies. Unset/`false` = shown (default). */
873
873
  disableMessageFeedback?: boolean;
874
+ /** Auto-send a suggestion chip's text on tap. Unset/`false` = fill the composer to edit (default). */
875
+ autoSendSuggestions?: boolean;
874
876
  }
875
877
  /**
876
878
  * Section: panel header actions — the enabled set (`history`/`close` buttons +
@@ -1187,6 +1189,8 @@ interface ResolvedOptions {
1187
1189
  showSources: boolean;
1188
1190
  /** Show 👍/👎 feedback controls on assistant replies. Default `true`. */
1189
1191
  enableMessageFeedback: boolean;
1192
+ /** Auto-send a suggestion chip's text on tap (vs. filling the composer to edit). Default `false`. */
1193
+ autoSendSuggestions: boolean;
1190
1194
  features: ResolvedFeatures;
1191
1195
  /** Resolved event-driven forms — ordered list + trigger index. */
1192
1196
  forms: ResolvedForms;
package/index.mjs CHANGED
@@ -29,7 +29,7 @@ var BRAND = {
29
29
  };
30
30
 
31
31
  // src/core/version.ts
32
- var ELEMENTS_VERSION = true ? "0.61.4" : "0.0.0-dev";
32
+ var ELEMENTS_VERSION = true ? "0.62.0" : "0.0.0-dev";
33
33
  var ELEMENTS_VERSION_PARAM = "_ev";
34
34
 
35
35
  // src/i18n/strings.ts
@@ -1105,6 +1105,7 @@ function resolveOptions(rawOpts) {
1105
1105
  showToolCalls: behavior.showToolCalls ?? false,
1106
1106
  showSources: behavior.showSources ?? false,
1107
1107
  enableMessageFeedback: !(behavior.disableMessageFeedback ?? false),
1108
+ autoSendSuggestions: behavior.autoSendSuggestions ?? false,
1108
1109
  features: {
1109
1110
  voice: opts.features?.voice ?? DEFAULT_FEATURES.voice,
1110
1111
  fileUpload: !(opts.features?.disableFileUpload ?? false),
@@ -1433,7 +1434,8 @@ var BEHAVIOR_ATTRS = [
1433
1434
  ["show-reasoning", "showReasoning", boolAttr],
1434
1435
  ["show-tool-calls", "showToolCalls", boolAttr],
1435
1436
  ["show-sources", "showSources", boolAttr],
1436
- ["disable-message-feedback", "disableMessageFeedback", boolAttr]
1437
+ ["disable-message-feedback", "disableMessageFeedback", boolAttr],
1438
+ ["auto-send-suggestions", "autoSendSuggestions", boolAttr]
1437
1439
  ];
1438
1440
  var I18N_ATTRS = [["locale", "locale"]];
1439
1441
  var ACTION_NAMES = new Set(ACTION_NAME_LITERALS);
@@ -2254,15 +2256,6 @@ function retryAfterMs(headers) {
2254
2256
  const ms = Number.isFinite(secs) ? secs * 1e3 : Date.parse(raw) - Date.now();
2255
2257
  return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 3e4) : null;
2256
2258
  }
2257
- function followupsFromMessages(messages) {
2258
- for (let i = messages.length - 1; i >= 0; i--) {
2259
- const m = messages[i];
2260
- if (!m || m.role !== "assistant") continue;
2261
- const part = m.parts.find((p36) => p36.type === "data-suggestions");
2262
- return part?.type === "data-suggestions" ? parseSuggestions(part.data) : void 0;
2263
- }
2264
- return void 0;
2265
- }
2266
2259
  function sleep(ms, signal7) {
2267
2260
  return new Promise((resolve) => {
2268
2261
  if (signal7?.aborted) return resolve();
@@ -2509,10 +2502,10 @@ var AgentTransport = class {
2509
2502
  canContinue: res.canContinue ?? true,
2510
2503
  messages,
2511
2504
  agent: res.agent,
2512
- // The latest turn's model follow-ups are persisted ON the assistant message (a `data-suggestions`
2513
- // part), not in a top-level field — read them off there, falling back to any server-provided
2514
- // top-level `suggestions` (e.g. resume chips) when the last turn carried none.
2515
- suggestions: followupsFromMessages(messages) ?? res.suggestions
2505
+ // Top-level STARTER chips (resume / new-chat), not turn-tied. A turn's own model follow-ups ride
2506
+ // ON its assistant message (`data-suggestions` part), lifted onto `message.suggestions` by
2507
+ // `fromWireMessage`; the App prefers the last turn's and falls back to these starters.
2508
+ suggestions: res.suggestions
2516
2509
  };
2517
2510
  }
2518
2511
  /**
@@ -3014,9 +3007,11 @@ function fromWireMessage(w) {
3014
3007
  }
3015
3008
  return null;
3016
3009
  }).filter((p36) => p36 !== null);
3010
+ const suggestionsPart = w.parts.find((p36) => p36.type === "data-suggestions");
3017
3011
  return {
3018
3012
  id: w.id,
3019
3013
  role: w.role,
3014
+ suggestions: suggestionsPart?.type === "data-suggestions" ? parseSuggestions(suggestionsPart.data) : void 0,
3020
3015
  // On reload the wire `id` IS the backend's id — keep it as `serverMessageId`
3021
3016
  // too so a HITL re-POST after a reload still references the backend's message.
3022
3017
  serverMessageId: w.id,
@@ -3239,6 +3234,7 @@ var StreamReducer = class {
3239
3234
  case "data-conversation-rebind":
3240
3235
  return;
3241
3236
  case "data-suggestions":
3237
+ m.suggestions = parseSuggestions(chunk.data);
3242
3238
  return;
3243
3239
  default: {
3244
3240
  const _exhaustive = chunk;
@@ -4532,6 +4528,13 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
4532
4528
  log11.info("attach (drop)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
4533
4529
  bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
4534
4530
  setAtts((prev) => [...prev, ...result.accepted]);
4531
+ },
4532
+ fill: (value) => {
4533
+ setText(value);
4534
+ const el = taRef.current;
4535
+ if (!el) return;
4536
+ el.focus({ preventScroll: true });
4537
+ requestAnimationFrame(() => el.setSelectionRange(el.value.length, el.value.length));
4535
4538
  }
4536
4539
  };
4537
4540
  return () => {
@@ -6896,10 +6899,13 @@ function Panel(props2) {
6896
6899
  el.removeEventListener("keydown", onKey);
6897
6900
  };
6898
6901
  }, [onClose]);
6899
- const composerAttachApiRef = useRef6(null);
6900
- const onDropItems = useCallback2((items) => {
6901
- composerAttachApiRef.current?.attachFromDrop(items);
6902
- }, []);
6902
+ const composerApiRef = props2.composerApiRef;
6903
+ const onDropItems = useCallback2(
6904
+ (items) => {
6905
+ composerApiRef.current?.attachFromDrop(items);
6906
+ },
6907
+ [composerApiRef]
6908
+ );
6903
6909
  const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });
6904
6910
  useDragMove(containerRef.current, options.mode === "modal");
6905
6911
  return /* @__PURE__ */ jsxs18(
@@ -6919,7 +6925,7 @@ function Panel(props2) {
6919
6925
  ...props2,
6920
6926
  containerEl: containerRef.current,
6921
6927
  dragOver,
6922
- composerAttachApiRef
6928
+ composerAttachApiRef: composerApiRef
6923
6929
  }
6924
6930
  ),
6925
6931
  /* @__PURE__ */ jsx23(PoweredByBar, { poweredBy: props2.options.poweredBy })
@@ -8236,6 +8242,7 @@ function App({ options, hostElement, bus }) {
8236
8242
  const resumeBubbleRef = useRef9(null);
8237
8243
  const regenerateDanglingRef = useRef9(null);
8238
8244
  const regenInFlightRef = useRef9(false);
8245
+ const composerApiRef = useRef9(null);
8239
8246
  const markActiveConversationReadRef = useRef9(null);
8240
8247
  const markReadTimerRef = useRef9();
8241
8248
  useEffect16(() => () => clearTimeout(markReadTimerRef.current), []);
@@ -8305,7 +8312,8 @@ function App({ options, hostElement, bus }) {
8305
8312
  if (loaded.length || tail.length) {
8306
8313
  messagesSig.value = [...withWelcomeRows(loaded, markers), ...tail];
8307
8314
  setCanSend(chat.canContinue ?? true);
8308
- setSuggestions(chat.suggestions ?? []);
8315
+ const lastTurn = loaded.findLast((m) => m.role === "assistant" && !m.ephemeral);
8316
+ setSuggestions(lastTurn?.suggestions ?? chat.suggestions ?? []);
8309
8317
  } else {
8310
8318
  messagesSig.value = withWelcomeRows([], markers);
8311
8319
  }
@@ -8439,11 +8447,10 @@ function App({ options, hostElement, bus }) {
8439
8447
  useEffect16(() => {
8440
8448
  if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
8441
8449
  }, [effectiveLocale, activeLocale]);
8442
- const captureSuggestions = useCallback6((chunk) => {
8443
- if (chunk.type !== "data-suggestions") return false;
8444
- setSuggestions(parseSuggestions(chunk.data));
8445
- return true;
8446
- }, []);
8450
+ const settleSuggestions = useCallback6(
8451
+ (turn, starters = []) => setSuggestions(turn.suggestions ?? starters),
8452
+ []
8453
+ );
8447
8454
  const adoptConversationRebind = useCallback6(
8448
8455
  (chunk) => {
8449
8456
  if (chunk.type !== "data-conversation-rebind") return false;
@@ -8465,7 +8472,7 @@ function App({ options, hostElement, bus }) {
8465
8472
  let bubble = null;
8466
8473
  try {
8467
8474
  for await (const evt of handle.iter) {
8468
- if (adoptConversationRebind(evt.chunk) || captureSuggestions(evt.chunk)) continue;
8475
+ if (adoptConversationRebind(evt.chunk)) continue;
8469
8476
  if (!bubble) {
8470
8477
  bubble = makeAssistantMessage();
8471
8478
  resumeBubbleRef.current = bubble;
@@ -8491,6 +8498,7 @@ function App({ options, hostElement, bus }) {
8491
8498
  bubble.status = "complete";
8492
8499
  feedback.play("messageReceived");
8493
8500
  emitMessage(bus, options, "assistant", assistantText(bubble));
8501
+ settleSuggestions(bubble);
8494
8502
  }
8495
8503
  }
8496
8504
  } catch (err) {
@@ -8635,7 +8643,7 @@ function App({ options, hostElement, bus }) {
8635
8643
  setActiveDetach(() => handle.detach);
8636
8644
  try {
8637
8645
  for await (const evt of handle.iter) {
8638
- if (adoptConversationRebind(evt.chunk) || captureSuggestions(evt.chunk)) continue;
8646
+ if (adoptConversationRebind(evt.chunk)) continue;
8639
8647
  reducer.apply(evt.chunk);
8640
8648
  if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) {
8641
8649
  setCanSend(false);
@@ -8654,6 +8662,7 @@ function App({ options, hostElement, bus }) {
8654
8662
  assistantMsg.status = "complete";
8655
8663
  feedback.play("messageReceived");
8656
8664
  emitMessage(bus, options, "assistant", assistantText(assistantMsg));
8665
+ settleSuggestions(assistantMsg);
8657
8666
  }
8658
8667
  } catch (error) {
8659
8668
  if (isAbortError(error)) {
@@ -9072,7 +9081,8 @@ function App({ options, hostElement, bus }) {
9072
9081
  transport.primeIdentity(visitorId, res.conversationId);
9073
9082
  if (res.agent) setAgent(res.agent);
9074
9083
  setCanSend(res.canContinue);
9075
- setSuggestions(res.suggestions ?? []);
9084
+ const lastTurn = hydrated.findLast((m) => m.role === "assistant" && !m.ephemeral);
9085
+ setSuggestions(lastTurn?.suggestions ?? res.suggestions ?? []);
9076
9086
  setView("chat");
9077
9087
  void resumeInFlight();
9078
9088
  await transport.markRead(targetConversationId);
@@ -9140,6 +9150,7 @@ function App({ options, hostElement, bus }) {
9140
9150
  feedback,
9141
9151
  bus,
9142
9152
  persistence,
9153
+ composerApiRef,
9143
9154
  view,
9144
9155
  canSend,
9145
9156
  isStreaming: streaming,
@@ -9165,9 +9176,10 @@ function App({ options, hostElement, bus }) {
9165
9176
  onStop: handleStop,
9166
9177
  onSuggestion: (s) => {
9167
9178
  const text = s.text ?? s.label;
9168
- log17.info("suggestion", { text });
9179
+ log17.info("suggestion", { text, autoSend: options.autoSendSuggestions });
9169
9180
  bus.emit("suggestion", { text });
9170
- void handleSend(text, []);
9181
+ if (options.autoSendSuggestions) void handleSend(text, []);
9182
+ else composerApiRef.current?.fill(text);
9171
9183
  },
9172
9184
  // Active form: replaces the composer as a gate and locks every chat action
9173
9185
  // until the visitor submits or (when skippable) skips.
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  ],
81
81
  "type": "module",
82
82
  "types": "./index.d.ts",
83
- "version": "0.61.4"
83
+ "version": "0.62.0"
84
84
  }
package/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-D9QqhMzi.js';
1
+ export { A as Asset, B as BlocksConfig, C as ConnectionConfig, a as ConnectionConfigPartial, E as Endpoints, H as HandshakeResponse, L as Link, P as PAGE_AREA_SUGGESTIONS, f as PageContext, S as ServerConfig, b as SiteConfig, U as UserContext, W as WidgetConfig, c as WidgetConfigPartial, d as WidgetSettings, e as WidgetSettingsPartial, g as assetSchema, h as blocksConfigSchema, i as connectionConfigPartialSchema, j as connectionConfigSchema, k as cssColorSchema, l as cssLengthSchema, m as endpointsSchema, n as handshakeResponseSchema, o as linkSchema, p as localeSchema, q as pageContextSchema, s as serverConfigSchema, r as siteConfigSchema, u as userContextSchema, t as uuid7Schema, w as widgetConfigPartialSchema, v as widgetConfigSchema, x as widgetSettingsPartialSchema, y as widgetSettingsSchema } from './deployment-i9cta98X.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -85,6 +85,7 @@ declare const behaviorSchema: z.ZodObject<{
85
85
  showToolCalls: z.ZodDefault<z.ZodBoolean>;
86
86
  showSources: z.ZodDefault<z.ZodBoolean>;
87
87
  disableMessageFeedback: z.ZodDefault<z.ZodBoolean>;
88
+ autoSendSuggestions: z.ZodDefault<z.ZodBoolean>;
88
89
  }, z.core.$loose>;
89
90
  type Behavior = z.infer<typeof behaviorSchema>;
90
91
 
package/schema.json CHANGED
@@ -489,6 +489,11 @@
489
489
  "default": false,
490
490
  "description": "Turn OFF the 👍/👎 feedback controls under each assistant reply. Unset/`false` = shown (default), `true` = hidden. A choice fires the `messageFeedback` event and is POSTed best-effort to `/pai/message-feedback`.",
491
491
  "type": "boolean"
492
+ },
493
+ "autoSendSuggestions": {
494
+ "default": false,
495
+ "description": "Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately.",
496
+ "type": "boolean"
492
497
  }
493
498
  },
494
499
  "required": [
@@ -497,7 +502,8 @@
497
502
  "showReasoning",
498
503
  "showToolCalls",
499
504
  "showSources",
500
- "disableMessageFeedback"
505
+ "disableMessageFeedback",
506
+ "autoSendSuggestions"
501
507
  ],
502
508
  "additionalProperties": {},
503
509
  "description": "Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).",
@@ -2360,6 +2366,11 @@
2360
2366
  "default": false,
2361
2367
  "description": "Turn OFF the 👍/👎 feedback controls under each assistant reply. Unset/`false` = shown (default), `true` = hidden. A choice fires the `messageFeedback` event and is POSTed best-effort to `/pai/message-feedback`.",
2362
2368
  "type": "boolean"
2369
+ },
2370
+ "autoSendSuggestions": {
2371
+ "default": false,
2372
+ "description": "Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately.",
2373
+ "type": "boolean"
2363
2374
  }
2364
2375
  },
2365
2376
  "required": [
@@ -2368,7 +2379,8 @@
2368
2379
  "showReasoning",
2369
2380
  "showToolCalls",
2370
2381
  "showSources",
2371
- "disableMessageFeedback"
2382
+ "disableMessageFeedback",
2383
+ "autoSendSuggestions"
2372
2384
  ],
2373
2385
  "additionalProperties": {},
2374
2386
  "description": "Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).",
package/schema.mjs CHANGED
@@ -133,6 +133,9 @@ var behaviorSchema = z4.object({
133
133
  ),
134
134
  disableMessageFeedback: z4.boolean().default(false).describe(
135
135
  "Turn OFF the \u{1F44D}/\u{1F44E} feedback controls under each assistant reply. Unset/`false` = shown (default), `true` = hidden. A choice fires the `messageFeedback` event and is POSTed best-effort to `/pai/message-feedback`."
136
+ ),
137
+ autoSendSuggestions: z4.boolean().default(false).describe(
138
+ "Auto-send a suggestion chip's text the moment it's tapped. `false` (default) fills the composer with the text so the visitor can edit before sending; `true` sends it immediately."
136
139
  )
137
140
  }).loose().describe("Conversation behaviour \u2014 lifecycle, streaming, and the transcript surface (scroll, feedback).").meta({
138
141
  examples: [
package/web-component.mjs CHANGED
@@ -1086,6 +1086,7 @@ function resolveOptions(rawOpts) {
1086
1086
  showToolCalls: behavior.showToolCalls ?? false,
1087
1087
  showSources: behavior.showSources ?? false,
1088
1088
  enableMessageFeedback: !(behavior.disableMessageFeedback ?? false),
1089
+ autoSendSuggestions: behavior.autoSendSuggestions ?? false,
1089
1090
  features: {
1090
1091
  voice: opts.features?.voice ?? DEFAULT_FEATURES.voice,
1091
1092
  fileUpload: !(opts.features?.disableFileUpload ?? false),
@@ -1414,7 +1415,8 @@ var BEHAVIOR_ATTRS = [
1414
1415
  ["show-reasoning", "showReasoning", boolAttr],
1415
1416
  ["show-tool-calls", "showToolCalls", boolAttr],
1416
1417
  ["show-sources", "showSources", boolAttr],
1417
- ["disable-message-feedback", "disableMessageFeedback", boolAttr]
1418
+ ["disable-message-feedback", "disableMessageFeedback", boolAttr],
1419
+ ["auto-send-suggestions", "autoSendSuggestions", boolAttr]
1418
1420
  ];
1419
1421
  var I18N_ATTRS = [["locale", "locale"]];
1420
1422
  var ACTION_NAMES = new Set(ACTION_NAME_LITERALS);
@@ -1930,7 +1932,7 @@ function createAuth(opts) {
1930
1932
  }
1931
1933
 
1932
1934
  // src/core/version.ts
1933
- var ELEMENTS_VERSION = true ? "0.61.4" : "0.0.0-dev";
1935
+ var ELEMENTS_VERSION = true ? "0.62.0" : "0.0.0-dev";
1934
1936
  var ELEMENTS_VERSION_PARAM = "_ev";
1935
1937
 
1936
1938
  // src/stream/types.ts
@@ -2213,15 +2215,6 @@ function retryAfterMs(headers) {
2213
2215
  const ms = Number.isFinite(secs) ? secs * 1e3 : Date.parse(raw) - Date.now();
2214
2216
  return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 3e4) : null;
2215
2217
  }
2216
- function followupsFromMessages(messages) {
2217
- for (let i = messages.length - 1; i >= 0; i--) {
2218
- const m = messages[i];
2219
- if (!m || m.role !== "assistant") continue;
2220
- const part = m.parts.find((p36) => p36.type === "data-suggestions");
2221
- return part?.type === "data-suggestions" ? parseSuggestions(part.data) : void 0;
2222
- }
2223
- return void 0;
2224
- }
2225
2218
  function sleep(ms, signal7) {
2226
2219
  return new Promise((resolve) => {
2227
2220
  if (signal7?.aborted) return resolve();
@@ -2468,10 +2461,10 @@ var AgentTransport = class {
2468
2461
  canContinue: res.canContinue ?? true,
2469
2462
  messages,
2470
2463
  agent: res.agent,
2471
- // The latest turn's model follow-ups are persisted ON the assistant message (a `data-suggestions`
2472
- // part), not in a top-level field — read them off there, falling back to any server-provided
2473
- // top-level `suggestions` (e.g. resume chips) when the last turn carried none.
2474
- suggestions: followupsFromMessages(messages) ?? res.suggestions
2464
+ // Top-level STARTER chips (resume / new-chat), not turn-tied. A turn's own model follow-ups ride
2465
+ // ON its assistant message (`data-suggestions` part), lifted onto `message.suggestions` by
2466
+ // `fromWireMessage`; the App prefers the last turn's and falls back to these starters.
2467
+ suggestions: res.suggestions
2475
2468
  };
2476
2469
  }
2477
2470
  /**
@@ -2973,9 +2966,11 @@ function fromWireMessage(w) {
2973
2966
  }
2974
2967
  return null;
2975
2968
  }).filter((p36) => p36 !== null);
2969
+ const suggestionsPart = w.parts.find((p36) => p36.type === "data-suggestions");
2976
2970
  return {
2977
2971
  id: w.id,
2978
2972
  role: w.role,
2973
+ suggestions: suggestionsPart?.type === "data-suggestions" ? parseSuggestions(suggestionsPart.data) : void 0,
2979
2974
  // On reload the wire `id` IS the backend's id — keep it as `serverMessageId`
2980
2975
  // too so a HITL re-POST after a reload still references the backend's message.
2981
2976
  serverMessageId: w.id,
@@ -3198,6 +3193,7 @@ var StreamReducer = class {
3198
3193
  case "data-conversation-rebind":
3199
3194
  return;
3200
3195
  case "data-suggestions":
3196
+ m.suggestions = parseSuggestions(chunk.data);
3201
3197
  return;
3202
3198
  default: {
3203
3199
  const _exhaustive = chunk;
@@ -4491,6 +4487,13 @@ function Composer({ options, transport, feedback, bus, isStreaming, onSend, onSt
4491
4487
  log10.info("attach (drop)", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
4492
4488
  bus.emit("attach", { count: result.accepted.length, totalBytes: totalBytes(result.accepted) });
4493
4489
  setAtts((prev) => [...prev, ...result.accepted]);
4490
+ },
4491
+ fill: (value) => {
4492
+ setText(value);
4493
+ const el = taRef.current;
4494
+ if (!el) return;
4495
+ el.focus({ preventScroll: true });
4496
+ requestAnimationFrame(() => el.setSelectionRange(el.value.length, el.value.length));
4494
4497
  }
4495
4498
  };
4496
4499
  return () => {
@@ -6855,10 +6858,13 @@ function Panel(props2) {
6855
6858
  el.removeEventListener("keydown", onKey);
6856
6859
  };
6857
6860
  }, [onClose]);
6858
- const composerAttachApiRef = useRef6(null);
6859
- const onDropItems = useCallback2((items) => {
6860
- composerAttachApiRef.current?.attachFromDrop(items);
6861
- }, []);
6861
+ const composerApiRef = props2.composerApiRef;
6862
+ const onDropItems = useCallback2(
6863
+ (items) => {
6864
+ composerApiRef.current?.attachFromDrop(items);
6865
+ },
6866
+ [composerApiRef]
6867
+ );
6862
6868
  const { visible: dragOver } = useFileDrop({ containerRef, onDrop: onDropItems });
6863
6869
  useDragMove(containerRef.current, options.mode === "modal");
6864
6870
  return /* @__PURE__ */ jsxs18(
@@ -6878,7 +6884,7 @@ function Panel(props2) {
6878
6884
  ...props2,
6879
6885
  containerEl: containerRef.current,
6880
6886
  dragOver,
6881
- composerAttachApiRef
6887
+ composerAttachApiRef: composerApiRef
6882
6888
  }
6883
6889
  ),
6884
6890
  /* @__PURE__ */ jsx23(PoweredByBar, { poweredBy: props2.options.poweredBy })
@@ -8195,6 +8201,7 @@ function App({ options, hostElement, bus }) {
8195
8201
  const resumeBubbleRef = useRef9(null);
8196
8202
  const regenerateDanglingRef = useRef9(null);
8197
8203
  const regenInFlightRef = useRef9(false);
8204
+ const composerApiRef = useRef9(null);
8198
8205
  const markActiveConversationReadRef = useRef9(null);
8199
8206
  const markReadTimerRef = useRef9();
8200
8207
  useEffect16(() => () => clearTimeout(markReadTimerRef.current), []);
@@ -8264,7 +8271,8 @@ function App({ options, hostElement, bus }) {
8264
8271
  if (loaded.length || tail.length) {
8265
8272
  messagesSig.value = [...withWelcomeRows(loaded, markers), ...tail];
8266
8273
  setCanSend(chat.canContinue ?? true);
8267
- setSuggestions(chat.suggestions ?? []);
8274
+ const lastTurn = loaded.findLast((m) => m.role === "assistant" && !m.ephemeral);
8275
+ setSuggestions(lastTurn?.suggestions ?? chat.suggestions ?? []);
8268
8276
  } else {
8269
8277
  messagesSig.value = withWelcomeRows([], markers);
8270
8278
  }
@@ -8398,11 +8406,10 @@ function App({ options, hostElement, bus }) {
8398
8406
  useEffect16(() => {
8399
8407
  if (effectiveLocale !== activeLocale) setActiveLocale(effectiveLocale);
8400
8408
  }, [effectiveLocale, activeLocale]);
8401
- const captureSuggestions = useCallback6((chunk) => {
8402
- if (chunk.type !== "data-suggestions") return false;
8403
- setSuggestions(parseSuggestions(chunk.data));
8404
- return true;
8405
- }, []);
8409
+ const settleSuggestions = useCallback6(
8410
+ (turn, starters = []) => setSuggestions(turn.suggestions ?? starters),
8411
+ []
8412
+ );
8406
8413
  const adoptConversationRebind = useCallback6(
8407
8414
  (chunk) => {
8408
8415
  if (chunk.type !== "data-conversation-rebind") return false;
@@ -8424,7 +8431,7 @@ function App({ options, hostElement, bus }) {
8424
8431
  let bubble = null;
8425
8432
  try {
8426
8433
  for await (const evt of handle.iter) {
8427
- if (adoptConversationRebind(evt.chunk) || captureSuggestions(evt.chunk)) continue;
8434
+ if (adoptConversationRebind(evt.chunk)) continue;
8428
8435
  if (!bubble) {
8429
8436
  bubble = makeAssistantMessage();
8430
8437
  resumeBubbleRef.current = bubble;
@@ -8450,6 +8457,7 @@ function App({ options, hostElement, bus }) {
8450
8457
  bubble.status = "complete";
8451
8458
  feedback.play("messageReceived");
8452
8459
  emitMessage(bus, options, "assistant", assistantText(bubble));
8460
+ settleSuggestions(bubble);
8453
8461
  }
8454
8462
  }
8455
8463
  } catch (err) {
@@ -8594,7 +8602,7 @@ function App({ options, hostElement, bus }) {
8594
8602
  setActiveDetach(() => handle.detach);
8595
8603
  try {
8596
8604
  for await (const evt of handle.iter) {
8597
- if (adoptConversationRebind(evt.chunk) || captureSuggestions(evt.chunk)) continue;
8605
+ if (adoptConversationRebind(evt.chunk)) continue;
8598
8606
  reducer.apply(evt.chunk);
8599
8607
  if (evt.chunk.type === "finish" && evt.chunk.canContinue === false) {
8600
8608
  setCanSend(false);
@@ -8613,6 +8621,7 @@ function App({ options, hostElement, bus }) {
8613
8621
  assistantMsg.status = "complete";
8614
8622
  feedback.play("messageReceived");
8615
8623
  emitMessage(bus, options, "assistant", assistantText(assistantMsg));
8624
+ settleSuggestions(assistantMsg);
8616
8625
  }
8617
8626
  } catch (error) {
8618
8627
  if (isAbortError(error)) {
@@ -9031,7 +9040,8 @@ function App({ options, hostElement, bus }) {
9031
9040
  transport.primeIdentity(visitorId, res.conversationId);
9032
9041
  if (res.agent) setAgent(res.agent);
9033
9042
  setCanSend(res.canContinue);
9034
- setSuggestions(res.suggestions ?? []);
9043
+ const lastTurn = hydrated.findLast((m) => m.role === "assistant" && !m.ephemeral);
9044
+ setSuggestions(lastTurn?.suggestions ?? res.suggestions ?? []);
9035
9045
  setView("chat");
9036
9046
  void resumeInFlight();
9037
9047
  await transport.markRead(targetConversationId);
@@ -9099,6 +9109,7 @@ function App({ options, hostElement, bus }) {
9099
9109
  feedback,
9100
9110
  bus,
9101
9111
  persistence,
9112
+ composerApiRef,
9102
9113
  view,
9103
9114
  canSend,
9104
9115
  isStreaming: streaming,
@@ -9124,9 +9135,10 @@ function App({ options, hostElement, bus }) {
9124
9135
  onStop: handleStop,
9125
9136
  onSuggestion: (s) => {
9126
9137
  const text = s.text ?? s.label;
9127
- log16.info("suggestion", { text });
9138
+ log16.info("suggestion", { text, autoSend: options.autoSendSuggestions });
9128
9139
  bus.emit("suggestion", { text });
9129
- void handleSend(text, []);
9140
+ if (options.autoSendSuggestions) void handleSend(text, []);
9141
+ else composerApiRef.current?.fill(text);
9130
9142
  },
9131
9143
  // Active form: replaces the composer as a gate and locks every chat action
9132
9144
  // until the visitor submits or (when skippable) skips.