@helpai/elements 0.19.1 → 0.19.3

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.mjs CHANGED
@@ -128,7 +128,7 @@ var STRINGS_EN = {
128
128
  formChooseAtMost: "Choose at most {max}",
129
129
  formOther: "Other",
130
130
  formOtherPlaceholder: "Type your answer\u2026",
131
- inputRequired: "Input required",
131
+ inputRequired: "I need a bit more info",
132
132
  inputSubmit: "Submit",
133
133
  inputSkip: "Skip",
134
134
  inputSubmitted: "Answer submitted",
@@ -238,7 +238,7 @@ var STRINGS_FR = {
238
238
  formChooseAtMost: "Choisissez au plus {max}",
239
239
  formOther: "Autre",
240
240
  formOtherPlaceholder: "Saisissez votre r\xE9ponse\u2026",
241
- inputRequired: "Saisie requise",
241
+ inputRequired: "J'ai besoin de quelques pr\xE9cisions",
242
242
  inputSubmit: "Envoyer",
243
243
  inputSkip: "Ignorer",
244
244
  inputSubmitted: "R\xE9ponse envoy\xE9e",
@@ -462,7 +462,7 @@ var DEFAULT_ATTACHMENTS = {
462
462
  maxCount: 10,
463
463
  accept: "*/*"
464
464
  };
465
- var DEFAULT_WELCOME = { animation: "typewriter", speed: 60 };
465
+ var DEFAULT_WELCOME = { animation: "instant", speed: 60 };
466
466
  var DEFAULT_FEATURES = {
467
467
  files: true,
468
468
  voice: "local",
@@ -1985,9 +1985,10 @@ function partFromReactive(p33) {
1985
1985
  import { signal as signal2 } from "@preact/signals";
1986
1986
 
1987
1987
  // src/stream/constants.ts
1988
- var ASK_USER_INPUT_TOOL = "tool:ask-user-input";
1989
1988
  function isAskUserInputTool(toolName) {
1990
- return toolName === ASK_USER_INPUT_TOOL;
1989
+ if (!toolName) return false;
1990
+ const name = toolName.startsWith("tool:") ? toolName.slice(5) : toolName;
1991
+ return name === "ask-user-input" || name === "ask_user_input" || name === "request-user-input" || name === "request_user_input";
1991
1992
  }
1992
1993
  var TRIGGER = {
1993
1994
  submitMessage: "submit-message",
@@ -4363,6 +4364,9 @@ function hardenLink(a) {
4363
4364
  import { useComputed as useComputed2, useSignal } from "@preact/signals";
4364
4365
  import { Fragment, jsx as jsx13, jsxs as jsxs10 } from "preact/jsx-runtime";
4365
4366
  var p12 = BRAND.cssPrefix;
4367
+ function displayToolName(toolName) {
4368
+ return (toolName.startsWith("tool:") ? toolName.slice(5) : toolName).replace(/[-_]+/g, " ");
4369
+ }
4366
4370
  function ToolApproval({ part, strings, active, onDecision }) {
4367
4371
  const approval = useComputed2(() => part.approvalSig.value);
4368
4372
  const input = useComputed2(() => part.inputSig.value);
@@ -4372,7 +4376,7 @@ function ToolApproval({ part, strings, active, onDecision }) {
4372
4376
  const ap = approval.value;
4373
4377
  return /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-approval ${p12}-tool-decided`, "data-testid": TID.toolDecision, children: [
4374
4378
  /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-decided-label`, "data-approved": ap?.approved ? "true" : "false", children: ap?.approved ? strings.approved : strings.rejected }),
4375
- /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: part.toolName }),
4379
+ /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: displayToolName(part.toolName) }),
4376
4380
  ap?.reason ? /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-decided-value`, children: ap.reason }) : null
4377
4381
  ] });
4378
4382
  }
@@ -4380,7 +4384,7 @@ function ToolApproval({ part, strings, active, onDecision }) {
4380
4384
  return /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-approval`, "data-testid": TID.toolApproval, children: [
4381
4385
  /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-head`, children: [
4382
4386
  /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-badge`, children: strings.approvalRequired }),
4383
- /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: part.toolName })
4387
+ /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: displayToolName(part.toolName) })
4384
4388
  ] }),
4385
4389
  args ? /* @__PURE__ */ jsx13("pre", { class: `${p12}-tool-args`, children: args }) : null,
4386
4390
  active ? /* @__PURE__ */ jsxs10(Fragment, { children: [
@@ -4490,10 +4494,17 @@ function num(v) {
4490
4494
  // src/ui/tool-ask-input.tsx
4491
4495
  import { jsx as jsx14, jsxs as jsxs11 } from "preact/jsx-runtime";
4492
4496
  var p13 = BRAND.cssPrefix;
4493
- function ToolAskInput({ part, strings, active, onSubmit }) {
4497
+ function ToolAskInput({ part, strings, active, onSubmit, onDecision }) {
4494
4498
  const state = useComputed3(() => part.stateSig.value);
4499
+ const approval = useComputed3(() => part.approvalSig.value);
4495
4500
  const request = useComputed3(() => parseAskUserInput(part.inputSig.value));
4496
- const decided = state.value === "output" || state.value === "error";
4501
+ const decided = state.value === "output" || state.value === "error" || approval.value?.approved !== void 0;
4502
+ const viaApproval = approval.value !== void 0;
4503
+ const resolve = (answer, accepted) => {
4504
+ if (viaApproval) onDecision(part.toolCallId, accepted, answer);
4505
+ else if (request.value.responseType === "confirmation") onSubmit(part.toolCallId, { confirmed: accepted });
4506
+ else onSubmit(part.toolCallId, accepted ? { answer } : {});
4507
+ };
4497
4508
  if (decided) {
4498
4509
  return /* @__PURE__ */ jsx14(DecidedSummary, { part, strings });
4499
4510
  }
@@ -4515,7 +4526,7 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4515
4526
  {
4516
4527
  type: "button",
4517
4528
  class: `${p13}-form-skip`,
4518
- onClick: () => onSubmit(part.toolCallId, { confirmed: false }),
4529
+ onClick: () => resolve(void 0, false),
4519
4530
  "data-testid": TID.toolInputSkip,
4520
4531
  children: strings.reject
4521
4532
  }
@@ -4525,7 +4536,7 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4525
4536
  {
4526
4537
  type: "button",
4527
4538
  class: `${p13}-form-submit`,
4528
- onClick: () => onSubmit(part.toolCallId, { confirmed: true }),
4539
+ onClick: () => resolve(void 0, true),
4529
4540
  "data-testid": TID.toolInputSubmit,
4530
4541
  children: strings.approve
4531
4542
  }
@@ -4536,9 +4547,9 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4536
4547
  fields: askInputToFields(req),
4537
4548
  strings,
4538
4549
  submitLabel: strings.inputSubmit,
4539
- onSubmit: (values) => onSubmit(part.toolCallId, values),
4550
+ onSubmit: (values) => resolve(values.answer ?? Object.values(values).join(", "), true),
4540
4551
  skipLabel: req.required === false ? strings.inputSkip : void 0,
4541
- onSkip: req.required === false ? () => onSubmit(part.toolCallId, {}) : void 0,
4552
+ onSkip: req.required === false ? () => resolve(void 0, false) : void 0,
4542
4553
  submitTestId: TID.toolInputSubmit,
4543
4554
  skipTestId: TID.toolInputSkip
4544
4555
  }
@@ -4553,10 +4564,12 @@ function Header({ strings, title }) {
4553
4564
  }
4554
4565
  function DecidedSummary({ part, strings }) {
4555
4566
  const output = useComputed3(() => part.outputSig.value);
4556
- const text = summarizeOutput(output.value) || strings.inputSubmitted;
4567
+ const approval = useComputed3(() => part.approvalSig.value);
4568
+ const answer = approval.value?.approved !== void 0 ? approval.value.reason ?? "" : summarizeOutput(output.value);
4569
+ const skipped = approval.value?.approved === false;
4557
4570
  return /* @__PURE__ */ jsxs11("div", { class: `${p13}-tool-ask-input ${p13}-tool-decided`, "data-testid": TID.toolDecision, children: [
4558
- /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-label`, children: strings.inputSubmitted }),
4559
- /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-value`, children: text })
4571
+ /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-label`, children: skipped ? strings.inputSkip : strings.inputSubmitted }),
4572
+ /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-value`, children: skipped ? "" : answer || strings.inputSubmitted })
4560
4573
  ] });
4561
4574
  }
4562
4575
  function summarizeOutput(output) {
@@ -4686,12 +4699,21 @@ function ToolPartView({
4686
4699
  const state = useComputed4(() => part.stateSig.value);
4687
4700
  const hasApproval = useComputed4(() => part.approvalSig.value !== void 0);
4688
4701
  if (tool?.humanInLoop) {
4702
+ if (isAskUserInputTool(part.toolName) || state.value === "awaiting-input") {
4703
+ return /* @__PURE__ */ jsx16(
4704
+ ToolAskInput,
4705
+ {
4706
+ part,
4707
+ strings,
4708
+ active: interactive,
4709
+ onSubmit: tool.onResult,
4710
+ onDecision: tool.onDecision
4711
+ }
4712
+ );
4713
+ }
4689
4714
  if (hasApproval.value || state.value === "awaiting-approval") {
4690
4715
  return /* @__PURE__ */ jsx16(ToolApproval, { part, strings, active: interactive, onDecision: tool.onDecision });
4691
4716
  }
4692
- if (isAskUserInputTool(part.toolName) || state.value === "awaiting-input") {
4693
- return /* @__PURE__ */ jsx16(ToolAskInput, { part, strings, active: interactive, onSubmit: tool.onResult });
4694
- }
4695
4717
  }
4696
4718
  return showToolCalls ? /* @__PURE__ */ jsx16(ToolChip, { part, strings }) : null;
4697
4719
  }
@@ -4754,19 +4776,23 @@ function MessageList({
4754
4776
  const [showJump, setShowJump] = useState6(false);
4755
4777
  const [scrolling, setScrolling] = useState6(false);
4756
4778
  const hasHydratedRef = useRef5(false);
4779
+ const detachedRef = useRef5(false);
4780
+ const autoPinAtRef = useRef5(0);
4781
+ const pinBottom = (el) => {
4782
+ autoPinAtRef.current = performance.now();
4783
+ el.scrollTop = el.scrollHeight;
4784
+ };
4757
4785
  const firstMessageId = messages.value[0]?.id;
4758
4786
  useLayoutEffect(() => {
4759
4787
  const el = ref.current;
4760
4788
  if (!el) return;
4761
4789
  if (messages.value.length === 0) return;
4762
4790
  hasHydratedRef.current = true;
4763
- const pinBottom = () => {
4764
- el.scrollTop = el.scrollHeight;
4765
- };
4766
- pinBottom();
4767
- const resizeObs = new ResizeObserver(pinBottom);
4791
+ const pinNow = () => pinBottom(el);
4792
+ pinNow();
4793
+ const resizeObs = new ResizeObserver(pinNow);
4768
4794
  resizeObs.observe(el);
4769
- const mutationObs = new MutationObserver(pinBottom);
4795
+ const mutationObs = new MutationObserver(pinNow);
4770
4796
  mutationObs.observe(el, { childList: true, subtree: true, characterData: true });
4771
4797
  const timerId = setTimeout(() => {
4772
4798
  resizeObs.disconnect();
@@ -4782,8 +4808,14 @@ function MessageList({
4782
4808
  const el = ref.current;
4783
4809
  if (!el) return;
4784
4810
  if (!hasHydratedRef.current) return;
4811
+ if (messages.value.at(-1)?.role === "user") {
4812
+ detachedRef.current = false;
4813
+ pinBottom(el);
4814
+ return;
4815
+ }
4816
+ if (detachedRef.current) return;
4785
4817
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
4786
- if (distanceFromBottom < STICK_THRESHOLD) el.scrollTop = el.scrollHeight;
4818
+ if (distanceFromBottom < STICK_THRESHOLD) pinBottom(el);
4787
4819
  }, [messages.value.length]);
4788
4820
  useEffect8(() => {
4789
4821
  const el = ref.current;
@@ -4791,8 +4823,9 @@ function MessageList({
4791
4823
  const last = messages.value.at(-1);
4792
4824
  if (!last) return;
4793
4825
  const pinIfNear = () => {
4826
+ if (detachedRef.current) return;
4794
4827
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
4795
- if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;
4828
+ if (distance < STICK_THRESHOLD * 2) pinBottom(el);
4796
4829
  };
4797
4830
  if (last.status !== "streaming") {
4798
4831
  const rafId = requestAnimationFrame(pinIfNear);
@@ -4806,8 +4839,8 @@ function MessageList({
4806
4839
  const activeFormId = form?.form.id;
4807
4840
  useLayoutEffect(() => {
4808
4841
  const el = ref.current;
4809
- if (!el || !activeFormId) return;
4810
- el.scrollTop = el.scrollHeight;
4842
+ if (!el || !activeFormId || detachedRef.current) return;
4843
+ pinBottom(el);
4811
4844
  }, [activeFormId]);
4812
4845
  useEffect8(() => {
4813
4846
  const el = ref.current;
@@ -4816,6 +4849,9 @@ function MessageList({
4816
4849
  let idleTimer;
4817
4850
  const onScroll = () => {
4818
4851
  syncJump();
4852
+ if (performance.now() - autoPinAtRef.current > 150) {
4853
+ detachedRef.current = el.scrollHeight - el.scrollTop - el.clientHeight > 8;
4854
+ }
4819
4855
  setScrolling(true);
4820
4856
  clearTimeout(idleTimer);
4821
4857
  idleTimer = setTimeout(() => setScrolling(false), DIVIDER_IDLE_MS);
@@ -4829,7 +4865,9 @@ function MessageList({
4829
4865
  }, []);
4830
4866
  const jumpToBottom = () => {
4831
4867
  const el = ref.current;
4832
- if (el) el.scrollTop = el.scrollHeight;
4868
+ if (!el) return;
4869
+ detachedRef.current = false;
4870
+ pinBottom(el);
4833
4871
  };
4834
4872
  const rows = [];
4835
4873
  let prevDay = "";
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  ],
81
81
  "type": "module",
82
82
  "types": "./index.d.ts",
83
- "version": "0.19.1"
83
+ "version": "0.19.3"
84
84
  }
package/schema.json CHANGED
@@ -911,7 +911,7 @@
911
911
  "type": "object",
912
912
  "properties": {
913
913
  "animation": {
914
- "default": "typewriter",
914
+ "default": "instant",
915
915
  "description": "How welcome messages render — `typewriter` types each message in character-by-character; `instant` shows them whole.",
916
916
  "type": "string",
917
917
  "enum": [
@@ -932,14 +932,14 @@
932
932
  "speed"
933
933
  ],
934
934
  "additionalProperties": {},
935
- "description": "How welcome messages render — typewriter effect with adjustable cps speed.",
935
+ "description": "How welcome messages render — instant by default; opt into a typewriter effect with adjustable cps speed.",
936
936
  "examples": [
937
937
  {
938
- "animation": "typewriter",
939
- "speed": 60
938
+ "animation": "instant"
940
939
  },
941
940
  {
942
- "animation": "instant"
941
+ "animation": "typewriter",
942
+ "speed": 60
943
943
  }
944
944
  ]
945
945
  },
@@ -2665,7 +2665,7 @@
2665
2665
  "type": "object",
2666
2666
  "properties": {
2667
2667
  "animation": {
2668
- "default": "typewriter",
2668
+ "default": "instant",
2669
2669
  "description": "How welcome messages render — `typewriter` types each message in character-by-character; `instant` shows them whole.",
2670
2670
  "type": "string",
2671
2671
  "enum": [
@@ -2686,14 +2686,14 @@
2686
2686
  "speed"
2687
2687
  ],
2688
2688
  "additionalProperties": {},
2689
- "description": "How welcome messages render — typewriter effect with adjustable cps speed.",
2689
+ "description": "How welcome messages render — instant by default; opt into a typewriter effect with adjustable cps speed.",
2690
2690
  "examples": [
2691
2691
  {
2692
- "animation": "typewriter",
2693
- "speed": 60
2692
+ "animation": "instant"
2694
2693
  },
2695
2694
  {
2696
- "animation": "instant"
2695
+ "animation": "typewriter",
2696
+ "speed": 60
2697
2697
  }
2698
2698
  ]
2699
2699
  },
package/schema.mjs CHANGED
@@ -345,12 +345,12 @@ var composerOptionsSchema = z10.object({
345
345
  import { z as z11 } from "zod";
346
346
  var welcomeAnimationSchema = z11.enum(["typewriter", "instant"]);
347
347
  var welcomeOptionsSchema = z11.object({
348
- animation: welcomeAnimationSchema.default("typewriter").describe(
348
+ animation: welcomeAnimationSchema.default("instant").describe(
349
349
  "How welcome messages render \u2014 `typewriter` types each message in character-by-character; `instant` shows them whole."
350
350
  ),
351
351
  speed: z11.number().int().min(1).max(2e3).default(60).describe("Typewriter speed in characters per second. Only meaningful when `animation = typewriter`.")
352
- }).loose().describe("How welcome messages render \u2014 typewriter effect with adjustable cps speed.").meta({
353
- examples: [{ animation: "typewriter", speed: 60 }, { animation: "instant" }]
352
+ }).loose().describe("How welcome messages render \u2014 instant by default; opt into a typewriter effect with adjustable cps speed.").meta({
353
+ examples: [{ animation: "instant" }, { animation: "typewriter", speed: 60 }]
354
354
  });
355
355
 
356
356
  // src/schema/widget/footer.ts
package/web-component.mjs CHANGED
@@ -128,7 +128,7 @@ var STRINGS_EN = {
128
128
  formChooseAtMost: "Choose at most {max}",
129
129
  formOther: "Other",
130
130
  formOtherPlaceholder: "Type your answer\u2026",
131
- inputRequired: "Input required",
131
+ inputRequired: "I need a bit more info",
132
132
  inputSubmit: "Submit",
133
133
  inputSkip: "Skip",
134
134
  inputSubmitted: "Answer submitted",
@@ -238,7 +238,7 @@ var STRINGS_FR = {
238
238
  formChooseAtMost: "Choisissez au plus {max}",
239
239
  formOther: "Autre",
240
240
  formOtherPlaceholder: "Saisissez votre r\xE9ponse\u2026",
241
- inputRequired: "Saisie requise",
241
+ inputRequired: "J'ai besoin de quelques pr\xE9cisions",
242
242
  inputSubmit: "Envoyer",
243
243
  inputSkip: "Ignorer",
244
244
  inputSubmitted: "R\xE9ponse envoy\xE9e",
@@ -447,7 +447,7 @@ var DEFAULT_ATTACHMENTS = {
447
447
  maxCount: 10,
448
448
  accept: "*/*"
449
449
  };
450
- var DEFAULT_WELCOME = { animation: "typewriter", speed: 60 };
450
+ var DEFAULT_WELCOME = { animation: "instant", speed: 60 };
451
451
  var DEFAULT_FEATURES = {
452
452
  files: true,
453
453
  voice: "local",
@@ -2040,9 +2040,10 @@ function partFromReactive(p33) {
2040
2040
  import { signal as signal2 } from "@preact/signals";
2041
2041
 
2042
2042
  // src/stream/constants.ts
2043
- var ASK_USER_INPUT_TOOL = "tool:ask-user-input";
2044
2043
  function isAskUserInputTool(toolName) {
2045
- return toolName === ASK_USER_INPUT_TOOL;
2044
+ if (!toolName) return false;
2045
+ const name = toolName.startsWith("tool:") ? toolName.slice(5) : toolName;
2046
+ return name === "ask-user-input" || name === "ask_user_input" || name === "request-user-input" || name === "request_user_input";
2046
2047
  }
2047
2048
  var TRIGGER = {
2048
2049
  submitMessage: "submit-message",
@@ -4418,6 +4419,9 @@ function hardenLink(a) {
4418
4419
  import { useComputed as useComputed2, useSignal } from "@preact/signals";
4419
4420
  import { Fragment, jsx as jsx13, jsxs as jsxs10 } from "preact/jsx-runtime";
4420
4421
  var p12 = BRAND.cssPrefix;
4422
+ function displayToolName(toolName) {
4423
+ return (toolName.startsWith("tool:") ? toolName.slice(5) : toolName).replace(/[-_]+/g, " ");
4424
+ }
4421
4425
  function ToolApproval({ part, strings, active, onDecision }) {
4422
4426
  const approval = useComputed2(() => part.approvalSig.value);
4423
4427
  const input = useComputed2(() => part.inputSig.value);
@@ -4427,7 +4431,7 @@ function ToolApproval({ part, strings, active, onDecision }) {
4427
4431
  const ap = approval.value;
4428
4432
  return /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-approval ${p12}-tool-decided`, "data-testid": TID.toolDecision, children: [
4429
4433
  /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-decided-label`, "data-approved": ap?.approved ? "true" : "false", children: ap?.approved ? strings.approved : strings.rejected }),
4430
- /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: part.toolName }),
4434
+ /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: displayToolName(part.toolName) }),
4431
4435
  ap?.reason ? /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-decided-value`, children: ap.reason }) : null
4432
4436
  ] });
4433
4437
  }
@@ -4435,7 +4439,7 @@ function ToolApproval({ part, strings, active, onDecision }) {
4435
4439
  return /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-approval`, "data-testid": TID.toolApproval, children: [
4436
4440
  /* @__PURE__ */ jsxs10("div", { class: `${p12}-tool-head`, children: [
4437
4441
  /* @__PURE__ */ jsx13("span", { class: `${p12}-tool-badge`, children: strings.approvalRequired }),
4438
- /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: part.toolName })
4442
+ /* @__PURE__ */ jsx13("strong", { class: `${p12}-tool-title`, children: displayToolName(part.toolName) })
4439
4443
  ] }),
4440
4444
  args ? /* @__PURE__ */ jsx13("pre", { class: `${p12}-tool-args`, children: args }) : null,
4441
4445
  active ? /* @__PURE__ */ jsxs10(Fragment, { children: [
@@ -4545,10 +4549,17 @@ function num(v) {
4545
4549
  // src/ui/tool-ask-input.tsx
4546
4550
  import { jsx as jsx14, jsxs as jsxs11 } from "preact/jsx-runtime";
4547
4551
  var p13 = BRAND.cssPrefix;
4548
- function ToolAskInput({ part, strings, active, onSubmit }) {
4552
+ function ToolAskInput({ part, strings, active, onSubmit, onDecision }) {
4549
4553
  const state = useComputed3(() => part.stateSig.value);
4554
+ const approval = useComputed3(() => part.approvalSig.value);
4550
4555
  const request = useComputed3(() => parseAskUserInput(part.inputSig.value));
4551
- const decided = state.value === "output" || state.value === "error";
4556
+ const decided = state.value === "output" || state.value === "error" || approval.value?.approved !== void 0;
4557
+ const viaApproval = approval.value !== void 0;
4558
+ const resolve = (answer, accepted) => {
4559
+ if (viaApproval) onDecision(part.toolCallId, accepted, answer);
4560
+ else if (request.value.responseType === "confirmation") onSubmit(part.toolCallId, { confirmed: accepted });
4561
+ else onSubmit(part.toolCallId, accepted ? { answer } : {});
4562
+ };
4552
4563
  if (decided) {
4553
4564
  return /* @__PURE__ */ jsx14(DecidedSummary, { part, strings });
4554
4565
  }
@@ -4570,7 +4581,7 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4570
4581
  {
4571
4582
  type: "button",
4572
4583
  class: `${p13}-form-skip`,
4573
- onClick: () => onSubmit(part.toolCallId, { confirmed: false }),
4584
+ onClick: () => resolve(void 0, false),
4574
4585
  "data-testid": TID.toolInputSkip,
4575
4586
  children: strings.reject
4576
4587
  }
@@ -4580,7 +4591,7 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4580
4591
  {
4581
4592
  type: "button",
4582
4593
  class: `${p13}-form-submit`,
4583
- onClick: () => onSubmit(part.toolCallId, { confirmed: true }),
4594
+ onClick: () => resolve(void 0, true),
4584
4595
  "data-testid": TID.toolInputSubmit,
4585
4596
  children: strings.approve
4586
4597
  }
@@ -4591,9 +4602,9 @@ function ToolAskInput({ part, strings, active, onSubmit }) {
4591
4602
  fields: askInputToFields(req),
4592
4603
  strings,
4593
4604
  submitLabel: strings.inputSubmit,
4594
- onSubmit: (values) => onSubmit(part.toolCallId, values),
4605
+ onSubmit: (values) => resolve(values.answer ?? Object.values(values).join(", "), true),
4595
4606
  skipLabel: req.required === false ? strings.inputSkip : void 0,
4596
- onSkip: req.required === false ? () => onSubmit(part.toolCallId, {}) : void 0,
4607
+ onSkip: req.required === false ? () => resolve(void 0, false) : void 0,
4597
4608
  submitTestId: TID.toolInputSubmit,
4598
4609
  skipTestId: TID.toolInputSkip
4599
4610
  }
@@ -4608,10 +4619,12 @@ function Header({ strings, title }) {
4608
4619
  }
4609
4620
  function DecidedSummary({ part, strings }) {
4610
4621
  const output = useComputed3(() => part.outputSig.value);
4611
- const text = summarizeOutput(output.value) || strings.inputSubmitted;
4622
+ const approval = useComputed3(() => part.approvalSig.value);
4623
+ const answer = approval.value?.approved !== void 0 ? approval.value.reason ?? "" : summarizeOutput(output.value);
4624
+ const skipped = approval.value?.approved === false;
4612
4625
  return /* @__PURE__ */ jsxs11("div", { class: `${p13}-tool-ask-input ${p13}-tool-decided`, "data-testid": TID.toolDecision, children: [
4613
- /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-label`, children: strings.inputSubmitted }),
4614
- /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-value`, children: text })
4626
+ /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-label`, children: skipped ? strings.inputSkip : strings.inputSubmitted }),
4627
+ /* @__PURE__ */ jsx14("span", { class: `${p13}-tool-decided-value`, children: skipped ? "" : answer || strings.inputSubmitted })
4615
4628
  ] });
4616
4629
  }
4617
4630
  function summarizeOutput(output) {
@@ -4741,12 +4754,21 @@ function ToolPartView({
4741
4754
  const state = useComputed4(() => part.stateSig.value);
4742
4755
  const hasApproval = useComputed4(() => part.approvalSig.value !== void 0);
4743
4756
  if (tool?.humanInLoop) {
4757
+ if (isAskUserInputTool(part.toolName) || state.value === "awaiting-input") {
4758
+ return /* @__PURE__ */ jsx16(
4759
+ ToolAskInput,
4760
+ {
4761
+ part,
4762
+ strings,
4763
+ active: interactive,
4764
+ onSubmit: tool.onResult,
4765
+ onDecision: tool.onDecision
4766
+ }
4767
+ );
4768
+ }
4744
4769
  if (hasApproval.value || state.value === "awaiting-approval") {
4745
4770
  return /* @__PURE__ */ jsx16(ToolApproval, { part, strings, active: interactive, onDecision: tool.onDecision });
4746
4771
  }
4747
- if (isAskUserInputTool(part.toolName) || state.value === "awaiting-input") {
4748
- return /* @__PURE__ */ jsx16(ToolAskInput, { part, strings, active: interactive, onSubmit: tool.onResult });
4749
- }
4750
4772
  }
4751
4773
  return showToolCalls ? /* @__PURE__ */ jsx16(ToolChip, { part, strings }) : null;
4752
4774
  }
@@ -4809,19 +4831,23 @@ function MessageList({
4809
4831
  const [showJump, setShowJump] = useState6(false);
4810
4832
  const [scrolling, setScrolling] = useState6(false);
4811
4833
  const hasHydratedRef = useRef5(false);
4834
+ const detachedRef = useRef5(false);
4835
+ const autoPinAtRef = useRef5(0);
4836
+ const pinBottom = (el) => {
4837
+ autoPinAtRef.current = performance.now();
4838
+ el.scrollTop = el.scrollHeight;
4839
+ };
4812
4840
  const firstMessageId = messages.value[0]?.id;
4813
4841
  useLayoutEffect(() => {
4814
4842
  const el = ref.current;
4815
4843
  if (!el) return;
4816
4844
  if (messages.value.length === 0) return;
4817
4845
  hasHydratedRef.current = true;
4818
- const pinBottom = () => {
4819
- el.scrollTop = el.scrollHeight;
4820
- };
4821
- pinBottom();
4822
- const resizeObs = new ResizeObserver(pinBottom);
4846
+ const pinNow = () => pinBottom(el);
4847
+ pinNow();
4848
+ const resizeObs = new ResizeObserver(pinNow);
4823
4849
  resizeObs.observe(el);
4824
- const mutationObs = new MutationObserver(pinBottom);
4850
+ const mutationObs = new MutationObserver(pinNow);
4825
4851
  mutationObs.observe(el, { childList: true, subtree: true, characterData: true });
4826
4852
  const timerId = setTimeout(() => {
4827
4853
  resizeObs.disconnect();
@@ -4837,8 +4863,14 @@ function MessageList({
4837
4863
  const el = ref.current;
4838
4864
  if (!el) return;
4839
4865
  if (!hasHydratedRef.current) return;
4866
+ if (messages.value.at(-1)?.role === "user") {
4867
+ detachedRef.current = false;
4868
+ pinBottom(el);
4869
+ return;
4870
+ }
4871
+ if (detachedRef.current) return;
4840
4872
  const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
4841
- if (distanceFromBottom < STICK_THRESHOLD) el.scrollTop = el.scrollHeight;
4873
+ if (distanceFromBottom < STICK_THRESHOLD) pinBottom(el);
4842
4874
  }, [messages.value.length]);
4843
4875
  useEffect8(() => {
4844
4876
  const el = ref.current;
@@ -4846,8 +4878,9 @@ function MessageList({
4846
4878
  const last = messages.value.at(-1);
4847
4879
  if (!last) return;
4848
4880
  const pinIfNear = () => {
4881
+ if (detachedRef.current) return;
4849
4882
  const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
4850
- if (distance < STICK_THRESHOLD * 2) el.scrollTop = el.scrollHeight;
4883
+ if (distance < STICK_THRESHOLD * 2) pinBottom(el);
4851
4884
  };
4852
4885
  if (last.status !== "streaming") {
4853
4886
  const rafId = requestAnimationFrame(pinIfNear);
@@ -4861,8 +4894,8 @@ function MessageList({
4861
4894
  const activeFormId = form?.form.id;
4862
4895
  useLayoutEffect(() => {
4863
4896
  const el = ref.current;
4864
- if (!el || !activeFormId) return;
4865
- el.scrollTop = el.scrollHeight;
4897
+ if (!el || !activeFormId || detachedRef.current) return;
4898
+ pinBottom(el);
4866
4899
  }, [activeFormId]);
4867
4900
  useEffect8(() => {
4868
4901
  const el = ref.current;
@@ -4871,6 +4904,9 @@ function MessageList({
4871
4904
  let idleTimer;
4872
4905
  const onScroll = () => {
4873
4906
  syncJump();
4907
+ if (performance.now() - autoPinAtRef.current > 150) {
4908
+ detachedRef.current = el.scrollHeight - el.scrollTop - el.clientHeight > 8;
4909
+ }
4874
4910
  setScrolling(true);
4875
4911
  clearTimeout(idleTimer);
4876
4912
  idleTimer = setTimeout(() => setScrolling(false), DIVIDER_IDLE_MS);
@@ -4884,7 +4920,9 @@ function MessageList({
4884
4920
  }, []);
4885
4921
  const jumpToBottom = () => {
4886
4922
  const el = ref.current;
4887
- if (el) el.scrollTop = el.scrollHeight;
4923
+ if (!el) return;
4924
+ detachedRef.current = false;
4925
+ pinBottom(el);
4888
4926
  };
4889
4927
  const rows = [];
4890
4928
  let prevDay = "";