@ykurilov/agent-chat-react 0.70.1 → 0.71.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @ykurilov/agent-chat-react
2
2
 
3
+ ## 0.71.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 519914e: Лента закрепляет только сообщения у текущего окна, пока следует за ответом. При расхождении измеренных высот DOM и оценок виртуального списка больше не монтируется весь остаток длинной истории. Растущий ответ и черновик правки сохраняют отдельное закрепление.
8
+
9
+ Прилипание к низу сохраняется, когда пересчёт высот сдвигает сообщение и прокрутку на одинаковое расстояние вверх, даже при росте списка. Ввод читателя вверх по-прежнему останавливает следование за ответом.
10
+
11
+ Служебные обёртки сообщений не анимируют координаты: глобальное правило уменьшенного движения с ненулевой длительностью перехода больше не создаёт пустоты между строками в Safari.
12
+
13
+ Рост ответа не перезаписывает ещё не обработанный уход читателя вверх: положение учитывается до доводки низа, даже если браузер задержал событие scroll.
14
+
15
+ После открытия и немедленной отправки начальная прокрутка больше не удерживает асинхронную цель внизу: уход читателя вверх сохраняется при дальнейших замерах ответа. Обновление ничего не требует.
16
+
17
+ ## 0.71.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 8b8c651: Три вида поля Одуванчик: сжатое, обычное и расширенное. Настройки модели/провайдера/рассуждений из каталога, цена и метки, инструменты приложения, шторка телефона. Сфера и токены из опубликованных ds-react/ds-tokens. Необязательный договор скина v29, сохранение уровня ответа для подписи в истории.
22
+
3
23
  ## 0.70.1
4
24
 
5
25
  No changes in this release.
@@ -367,6 +367,7 @@ var russianLabels = {
367
367
  notices: russianNotices,
368
368
  messengerStatus: russianMessengerStatus,
369
369
  workView: russianWorkView,
370
+ composerSettings: { levels: { off: "\u0412\u044B\u043A\u043B", on: "\u0412\u043A\u043B", minimal: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C", low: "\u041D\u0438\u0437\u043A\u043E", medium: "\u0421\u0440\u0435\u0434\u043D\u0435", high: "\u0412\u044B\u0441\u043E\u043A\u043E", max: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C" }, model: "\u041C\u043E\u0434\u0435\u043B\u044C", reasoning: "\u0420\u0430\u0441\u0441\u0443\u0436\u0434\u0435\u043D\u0438\u044F", tools: "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B", settings: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043E\u0442\u0432\u0435\u0442\u0430", done: "\u0413\u043E\u0442\u043E\u0432\u043E", via: "\u0427\u0435\u0440\u0435\u0437", search: "\u041D\u0430\u0439\u0442\u0438 \u043C\u043E\u0434\u0435\u043B\u044C", showReasoning: "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0445\u043E\u0434 \u043C\u044B\u0441\u043B\u0438", project: "\u041F\u0440\u043E\u0435\u043A\u0442", file: "\u0424\u0430\u0439\u043B", image: "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430", textModels: "\u0422\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438", imageModels: "\u0422\u0435\u043A\u0441\u0442 \u0438 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F", models: "\u041C\u043E\u0434\u0435\u043B\u0438", noResults: "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E", price: "USD \u0437\u0430 \u043C\u0438\u043B\u043B\u0438\u043E\u043D \u0442\u043E\u043A\u0435\u043D\u043E\u0432", input: "\u0432\u0445\u043E\u0434", output: "\u0432\u044B\u0445\u043E\u0434" },
370
371
  composerPlaceholder: "\u041D\u0430\u043F\u0438\u0448\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
371
372
  composerLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
372
373
  send: "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C",
@@ -683,6 +684,7 @@ var englishLabels = {
683
684
  locale: "en",
684
685
  ...englishWorkLabels,
685
686
  workView: englishWorkViewLabels,
687
+ composerSettings: { levels: { off: "Off", on: "On", minimal: "Minimal", low: "Low", medium: "Medium", high: "High", max: "Max" }, model: "Model", reasoning: "Reasoning", tools: "Tools", settings: "Response settings", done: "Done", via: "Via", search: "Find model", showReasoning: "Show reasoning", project: "Project", file: "File", image: "Image", textModels: "Text tasks", imageModels: "Text and images", models: "Models", noResults: "No results", price: "USD per million tokens", input: "input", output: "output" },
686
688
  composerPlaceholder: "Write a message",
687
689
  composerLabel: "Message",
688
690
  send: "Send",
@@ -961,6 +963,7 @@ function ChatLabelsProvider({ labels, children }) {
961
963
  const resolved = useMemo(
962
964
  () => ({
963
965
  ...labels,
966
+ composerSettings: { ...(labels.locale?.startsWith("en") ? englishLabels : russianLabels).composerSettings, ...labels.composerSettings },
964
967
  externalImageLink: labels.externalImageLink ?? russianLabels.externalImageLink,
965
968
  answerCutByLength: labels.answerCutByLength ?? russianLabels.answerCutByLength,
966
969
  answerEdited: labels.answerEdited ?? russianLabels.answerEdited,
@@ -3886,6 +3889,10 @@ function useModelChoice({ api, chatId, savedChoice, savedChoicePending = false,
3886
3889
  models: provider.models.map((model) => ({
3887
3890
  id: model.id,
3888
3891
  name: model.displayName,
3892
+ ...model.group !== void 0 && { group: model.group },
3893
+ ...model.description !== void 0 && { description: model.description },
3894
+ ...model.tags !== void 0 && { tags: model.tags },
3895
+ ...model.price !== void 0 && { price: model.price },
3889
3896
  ...typeof model.acceptsImages === "boolean" && { acceptsImages: model.acceptsImages }
3890
3897
  }))
3891
3898
  })),
@@ -4190,7 +4197,10 @@ var SCROLL_KEYS = /* @__PURE__ */ new Set([...JUMP_KEYS, "PageUp", "ArrowUp", "H
4190
4197
  var READER_INPUT_WINDOW_MS = 250;
4191
4198
  var UP_KEYS = /* @__PURE__ */ new Set(["PageUp", "ArrowUp", "Home"]);
4192
4199
  function FeedItem({ style, children, ref }) {
4193
- return /* @__PURE__ */ jsx3("div", { ref, style, "data-feed-item": "", children });
4200
+ return /* @__PURE__ */ jsx3("div", { ref, style: { ...style, transitionProperty: "none" }, "data-feed-item": "", children });
4201
+ }
4202
+ function FeedWindow({ style, children, ref }) {
4203
+ return /* @__PURE__ */ jsx3("div", { ref, style: { ...style, transitionProperty: "none" }, children });
4194
4204
  }
4195
4205
  function isTextField(target) {
4196
4206
  if (!(target instanceof Element)) return false;
@@ -4207,6 +4217,7 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4207
4217
  }, []);
4208
4218
  const lastHeight = useRef7(0);
4209
4219
  const lastTop = useRef7(0);
4220
+ const syncScrollState = useRef7(null);
4210
4221
  const shrunkSinceScroll = useRef7(0);
4211
4222
  const openedAtBottom = useRef7(false);
4212
4223
  const userJumpUntil = useRef7(0);
@@ -4223,43 +4234,60 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4223
4234
  const lastIndex = messages.length - 1;
4224
4235
  useEffect6(() => setScrollerReady(true), []);
4225
4236
  useLayoutEffect(() => {
4226
- const scroller = scrollRef.current;
4237
+ const scroller2 = scrollRef.current;
4227
4238
  const root = rootRef.current;
4228
- if (!scrollerReady || !scroller || !root) return;
4239
+ if (!scrollerReady || !scroller2 || !root) return;
4229
4240
  const measure = () => {
4230
- const offset = root.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop;
4241
+ const offset = root.getBoundingClientRect().top - scroller2.getBoundingClientRect().top + scroller2.scrollTop;
4231
4242
  setStartMargin(Math.max(0, Math.round(offset)));
4232
4243
  };
4233
4244
  measure();
4234
4245
  const observer = new ResizeObserver(measure);
4235
- observer.observe(scroller);
4246
+ observer.observe(scroller2);
4236
4247
  return () => observer.disconnect();
4237
4248
  }, [scrollRef, scrollerReady]);
4238
4249
  useEffect6(() => {
4239
- const scroller = scrollRef.current;
4240
- if (!scrollerReady || !scroller) return;
4241
- lastHeight.current = scroller.scrollHeight;
4242
- lastTop.current = scroller.scrollTop;
4250
+ const scroller2 = scrollRef.current;
4251
+ if (!scrollerReady || !scroller2) return;
4252
+ lastHeight.current = scroller2.scrollHeight;
4253
+ lastTop.current = scroller2.scrollTop;
4254
+ let anchor;
4255
+ const rememberAnchor = () => {
4256
+ const view = scroller2.getBoundingClientRect();
4257
+ for (const element of rootRef.current?.querySelectorAll("[data-feed-item]") ?? []) {
4258
+ if (element.style.visibility === "hidden") continue;
4259
+ const box = element.getBoundingClientRect();
4260
+ if (box.bottom > view.top && box.top < view.bottom) {
4261
+ anchor = { element, offset: box.top - view.top + scroller2.scrollTop };
4262
+ return;
4263
+ }
4264
+ }
4265
+ anchor = void 0;
4266
+ };
4267
+ rememberAnchor();
4243
4268
  const onScroll = () => {
4244
- const height = scroller.scrollHeight;
4245
- const top = scroller.scrollTop;
4246
- const near = Math.min(height, lastHeight.current) - top - scroller.clientHeight < STICK_TO_BOTTOM_PX;
4269
+ const height = scroller2.scrollHeight;
4270
+ const top = scroller2.scrollTop;
4271
+ const near = Math.min(height, lastHeight.current) - top - scroller2.clientHeight < STICK_TO_BOTTOM_PX;
4247
4272
  const shrunk = shrunkSinceScroll.current + Math.max(0, lastHeight.current - height);
4248
4273
  shrunkSinceScroll.current = 0;
4249
4274
  const step = top - lastTop.current;
4250
4275
  const now = performance.now();
4251
4276
  const readerActed = now < readerInputUntil.current || now < userJumpUntil.current;
4252
- const movedByLayout = step < 0 && -step <= 2 * shrunk && !readerActed;
4277
+ const anchorShift = anchor && scroller2.contains(anchor.element) ? anchor.element.getBoundingClientRect().top - scroller2.getBoundingClientRect().top + top - anchor.offset : 0;
4278
+ const compensated = anchorShift < 0 && Math.abs(step - anchorShift) <= 1;
4279
+ const movedByLayout = step < 0 && (-step <= 2 * shrunk || compensated) && !readerActed;
4253
4280
  lastTop.current = top;
4254
4281
  lastHeight.current = height;
4282
+ rememberAnchor();
4255
4283
  if (movedByLayout) {
4256
- if (stuck.current) scroller.scrollTop = height;
4284
+ if (stuck.current) scroller2.scrollTop = height;
4257
4285
  return;
4258
4286
  }
4259
4287
  if (step < 0) stick(readerActed ? false : near);
4260
4288
  else if (step > 0) {
4261
4289
  readerUpUntil.current = 0;
4262
- if (near && (step <= scroller.clientHeight || performance.now() < userJumpUntil.current)) stick(true);
4290
+ if (near && (step <= scroller2.clientHeight || performance.now() < userJumpUntil.current)) stick(true);
4263
4291
  }
4264
4292
  };
4265
4293
  const onKeyDown = (event) => {
@@ -4288,38 +4316,41 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4288
4316
  const onPointerUp = () => {
4289
4317
  if (userJumpUntil.current > performance.now() + POINTER_JUMP_WINDOW_MS) userJumpUntil.current = performance.now() + POINTER_JUMP_WINDOW_MS;
4290
4318
  };
4291
- scroller.addEventListener("scroll", onScroll, { passive: true });
4319
+ syncScrollState.current = onScroll;
4320
+ scroller2.addEventListener("scroll", onScroll, { passive: true });
4292
4321
  document.addEventListener("keydown", onKeyDown);
4293
- scroller.addEventListener("pointerdown", onPointerDown);
4294
- scroller.addEventListener("wheel", onWheel, { passive: true });
4295
- scroller.addEventListener("touchstart", onTouchStart, { passive: true });
4296
- scroller.addEventListener("touchmove", onTouchMove, { passive: true });
4322
+ scroller2.addEventListener("pointerdown", onPointerDown);
4323
+ scroller2.addEventListener("wheel", onWheel, { passive: true });
4324
+ scroller2.addEventListener("touchstart", onTouchStart, { passive: true });
4325
+ scroller2.addEventListener("touchmove", onTouchMove, { passive: true });
4297
4326
  window.addEventListener("pointerup", onPointerUp);
4298
4327
  window.addEventListener("pointercancel", onPointerUp);
4299
4328
  return () => {
4300
- scroller.removeEventListener("scroll", onScroll);
4329
+ syncScrollState.current = null;
4330
+ scroller2.removeEventListener("scroll", onScroll);
4301
4331
  document.removeEventListener("keydown", onKeyDown);
4302
- scroller.removeEventListener("pointerdown", onPointerDown);
4303
- scroller.removeEventListener("wheel", onWheel);
4304
- scroller.removeEventListener("touchstart", onTouchStart);
4305
- scroller.removeEventListener("touchmove", onTouchMove);
4332
+ scroller2.removeEventListener("pointerdown", onPointerDown);
4333
+ scroller2.removeEventListener("wheel", onWheel);
4334
+ scroller2.removeEventListener("touchstart", onTouchStart);
4335
+ scroller2.removeEventListener("touchmove", onTouchMove);
4306
4336
  window.removeEventListener("pointerup", onPointerUp);
4307
4337
  window.removeEventListener("pointercancel", onPointerUp);
4308
4338
  };
4309
4339
  }, [scrollRef, scrollerReady, stick]);
4310
4340
  useEffect6(() => {
4311
- const scroller = scrollRef.current;
4341
+ const scroller2 = scrollRef.current;
4312
4342
  const root = rootRef.current;
4313
- if (!scrollerReady || !scroller || !root) return;
4343
+ if (!scrollerReady || !scroller2 || !root) return;
4314
4344
  const settleAtBottom = () => {
4315
- const height = scroller.scrollHeight;
4316
- const clientHeight = scroller.clientHeight;
4345
+ if (scroller2.scrollTop < lastTop.current) syncScrollState.current?.();
4346
+ const height = scroller2.scrollHeight;
4347
+ const clientHeight = scroller2.clientHeight;
4317
4348
  shrunkSinceScroll.current += Math.max(0, lastHeight.current - height);
4318
4349
  lastHeight.current = height;
4319
4350
  const needed = height > settledHeight.current || clientHeight < settledClientHeight.current;
4320
4351
  settledHeight.current = height;
4321
4352
  settledClientHeight.current = clientHeight;
4322
- if (stuck.current && needed && performance.now() >= readerUpUntil.current) scroller.scrollTop = height;
4353
+ if (stuck.current && needed && performance.now() >= readerUpUntil.current) scroller2.scrollTop = height;
4323
4354
  };
4324
4355
  let followUp = 0;
4325
4356
  const follow = () => {
@@ -4334,7 +4365,7 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4334
4365
  const listObserver = new MutationObserver(follow);
4335
4366
  listObserver.observe(root, { subtree: true, childList: true, attributes: true, attributeFilter: ["style"] });
4336
4367
  const scrollerObserver = new ResizeObserver(follow);
4337
- scrollerObserver.observe(scroller);
4368
+ scrollerObserver.observe(scroller2);
4338
4369
  return () => {
4339
4370
  listObserver.disconnect();
4340
4371
  scrollerObserver.disconnect();
@@ -4349,8 +4380,8 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4349
4380
  setPositioned(false);
4350
4381
  return;
4351
4382
  }
4352
- const scroller = scrollRef.current;
4353
- if (!scrollerReady || !scroller || openedAtBottom.current) return;
4383
+ const scroller2 = scrollRef.current;
4384
+ if (!scrollerReady || !scroller2 || openedAtBottom.current) return;
4354
4385
  openedAtBottom.current = true;
4355
4386
  const focusIndex = openAtMessageId === void 0 ? -1 : messages.findIndex((message) => message.id === openAtMessageId);
4356
4387
  stick(focusIndex < 0 || focusIndex === lastIndex);
@@ -4358,12 +4389,14 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4358
4389
  virtualizerRef.current?.scrollToIndex(focusIndex, { align: "start" });
4359
4390
  openingScrollUntil.current = performance.now() + OPENING_SCROLL_QUIET_MS;
4360
4391
  } else {
4361
- virtualizerRef.current?.scrollTo(BOTTOM_SCROLL_TARGET);
4392
+ queueMicrotask(() => {
4393
+ if (rootRef.current?.isConnected && scrollRef.current === scroller2 && stuck.current) scroller2.scrollTop = scroller2.scrollHeight;
4394
+ });
4362
4395
  openingScrollUntil.current = 0;
4363
4396
  }
4364
4397
  let frame = 0;
4365
4398
  const cancel = () => {
4366
- scroller.removeEventListener("scroll", settle);
4399
+ scroller2.removeEventListener("scroll", settle);
4367
4400
  cancelAnimationFrame(frame);
4368
4401
  cancelSettle.current = null;
4369
4402
  };
@@ -4372,7 +4405,7 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4372
4405
  setPositioned(true);
4373
4406
  };
4374
4407
  cancelSettle.current = cancel;
4375
- scroller.addEventListener("scroll", settle, { once: true });
4408
+ scroller2.addEventListener("scroll", settle, { once: true });
4376
4409
  frame = requestAnimationFrame(() => {
4377
4410
  frame = requestAnimationFrame(settle);
4378
4411
  });
@@ -4382,19 +4415,22 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4382
4415
  stick(true);
4383
4416
  if (performance.now() < openingScrollUntil.current) virtualizerRef.current?.scrollTo(BOTTOM_SCROLL_TARGET);
4384
4417
  openingScrollUntil.current = 0;
4385
- const scroller = scrollRef.current;
4386
- if (scroller) scroller.scrollTop = scroller.scrollHeight;
4418
+ const scroller2 = scrollRef.current;
4419
+ if (scroller2) scroller2.scrollTop = scroller2.scrollHeight;
4387
4420
  }, [scrollRef, stick]);
4388
4421
  useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
4389
4422
  const virtualizer = virtualizerRef.current;
4390
4423
  const editedIndex = keepMountedMessageId === void 0 ? -1 : messages.findIndex((message) => message.id === keepMountedMessageId);
4391
- const tailStart = positioned && followingBottom && virtualizer && lastIndex >= 0 ? Math.min(lastIndex, Math.max(0, virtualizer.findItemIndex(virtualizer.scrollOffset - BUFFER_PX))) : lastIndex + 1;
4424
+ const scroller = scrollRef.current;
4425
+ const keepBuffer = positioned && followingBottom && virtualizer && scroller && lastIndex >= 0;
4426
+ const bufferStart = keepBuffer ? Math.max(0, virtualizer.findItemIndex(scroller.scrollTop - BUFFER_PX)) : 0;
4427
+ const bufferEnd = keepBuffer ? Math.min(lastIndex, virtualizer.findItemIndex(scroller.scrollTop + scroller.clientHeight)) : -1;
4392
4428
  const keepMounted = useMemo6(() => {
4393
- const kept = new Set(Array.from({ length: Math.max(0, lastIndex + 1 - tailStart) }, (_, offset) => tailStart + offset));
4429
+ const kept = new Set(Array.from({ length: Math.max(0, bufferEnd - bufferStart + 1) }, (_, offset) => bufferStart + offset));
4394
4430
  if (keepLastMounted && lastIndex >= 0) kept.add(lastIndex);
4395
4431
  if (editedIndex >= 0) kept.add(editedIndex);
4396
4432
  return [...kept].sort((a, b) => a - b);
4397
- }, [editedIndex, keepLastMounted, lastIndex, tailStart]);
4433
+ }, [bufferEnd, bufferStart, editedIndex, keepLastMounted, lastIndex]);
4398
4434
  return /* @__PURE__ */ jsx3("div", { ref: rootRef, children: scrollerReady && /* @__PURE__ */ jsx3(
4399
4435
  Virtualizer,
4400
4436
  {
@@ -4404,6 +4440,7 @@ function MessageFeedView({ scrollRef, messages, renderMessage, keepLastMounted,
4404
4440
  itemSize: UNMEASURED_MESSAGE_HEIGHT_PX,
4405
4441
  bufferSize: positioned ? BUFFER_PX : 0,
4406
4442
  item: FeedItem,
4443
+ as: FeedWindow,
4407
4444
  keepMounted,
4408
4445
  startMargin,
4409
4446
  children: (message, index) => renderMessage(message, index)
@@ -5784,7 +5821,8 @@ function workLayoutOf(entries, { streaming, phrases, labels, thoughts = false })
5784
5821
  }
5785
5822
  if (streaming && lastStep > lastShown) {
5786
5823
  const live = liveStepOf(group, phrases, labels);
5787
- layout.push({ kind: "live", key, phrase: labels.live(live.phrase), icon: live.icon });
5824
+ const completed = doneLinesOf((thoughts ? group : group.filter((step) => step.kind === "tool")).filter((step) => step.kind === "tool" && step.call.state !== "running"), phrases, labels, false).items;
5825
+ layout.push({ kind: "live", key, phrase: labels.live(live.phrase), icon: live.icon, ...completed.length > 0 && { completed } });
5788
5826
  } else {
5789
5827
  const done = doneLinesOf(thoughts ? group : group.filter((step) => step.kind === "tool"), phrases, labels, !streaming);
5790
5828
  if (done.count > 0) layout.push({ kind: "done", key, ...done });
@@ -5846,7 +5884,7 @@ function WorkStepsPart({
5846
5884
  const labels = useChatLabels();
5847
5885
  const [open, setOpen] = usePartOpen(expanded, `${item.key}:work`);
5848
5886
  if (skin.WorkSteps) {
5849
- return item.kind === "live" ? /* @__PURE__ */ jsx8(skin.WorkSteps, { state: "live", phrase: item.phrase, icon: item.icon }) : /* @__PURE__ */ jsx8(
5887
+ return item.kind === "live" ? /* @__PURE__ */ jsx8(skin.WorkSteps, { state: "live", phrase: item.phrase, icon: item.icon, ...item.completed && { completed: item.completed } }) : /* @__PURE__ */ jsx8(
5850
5888
  skin.WorkSteps,
5851
5889
  {
5852
5890
  state: "done",
@@ -5881,7 +5919,7 @@ function SubjectAgentChat(props) {
5881
5919
  if (found.state === "failed") return /* @__PURE__ */ jsx9(props.skin.ErrorNotice, { kind: "other", onRetry: found.retry });
5882
5920
  return /* @__PURE__ */ jsx9(SubjectChatLoading, { skin: props.skin });
5883
5921
  }
5884
- function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attachments: attachmentsPlugin, spendingLimits, onBusyChange, appBridge, initialDraft, answerEditing, onFeedChange, messageActions, onTurnFinished, onTurnRefused, composerActions, onAppEvent, onChatAccessChange, onStateChange, onModelChosen, onChatUnavailable, reloadKey, steering, widgets, diagrams, liveDemos, images, videos, appEvents, workView, stepPhrases, noticeLines }) {
5922
+ function AgentChatBody({ composerOptions, api, chatId, skin, liveness, voice, voiceDialog, attachments: attachmentsPlugin, spendingLimits, onBusyChange, appBridge, initialDraft, answerEditing, onFeedChange, messageActions, onTurnFinished, onTurnRefused, composerActions, onAppEvent, onChatAccessChange, onStateChange, onModelChosen, onChatUnavailable, reloadKey, steering, widgets, diagrams, liveDemos, images, videos, appEvents, workView, stepPhrases, noticeLines, answerDetails }) {
5885
5923
  const spendingNoticeId = useId2();
5886
5924
  const closedNoticeId = useId2();
5887
5925
  const chat = useAgentChat({
@@ -5913,8 +5951,17 @@ function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attach
5913
5951
  const attachments = attachmentsPlugin?.useController({ api, chatId, modelSeesImages: chatModelSeesImages });
5914
5952
  const describeFiles = attachments?.describe;
5915
5953
  const imageClients = useSameList(imageClientsOf(images));
5954
+ const [localReasoningVisible, setLocalReasoningVisible] = useState18(true);
5955
+ const showReasoning = composerOptions?.reasoningVisibility?.value ?? localReasoningVisible;
5956
+ const effectiveComposerOptions = useMemo16(() => ({ ...composerOptions, reasoningVisibility: composerOptions?.reasoningVisibility ?? { value: showReasoning, onChange: setLocalReasoningVisible } }), [composerOptions, showReasoning]);
5957
+ const showAnswerDetails = answerDetails ?? composerOptions?.variant === "extended";
5958
+ const answerNamesKey = showAnswerDetails ? JSON.stringify(modelChoice.providers.map(({ id, name, models }) => ({ id, name, models: models.map(({ id: id2, name: name2 }) => ({ id: id2, name: name2 })) }))) : "";
5959
+ const answerModelNames = useMemo16(() => answerNamesKey ? JSON.parse(answerNamesKey) : void 0, [answerNamesKey]);
5916
5960
  const extensions = useMemo16(
5917
5961
  () => ({
5962
+ reasoningVisible: showReasoning,
5963
+ answerDetails: showAnswerDetails,
5964
+ ...answerModelNames && { modelNames: answerModelNames },
5918
5965
  ...widgets && { widgets },
5919
5966
  ...diagrams && { diagrams },
5920
5967
  ...liveDemos && { liveDemos },
@@ -5925,7 +5972,7 @@ function AgentChatBody({ api, chatId, skin, liveness, voice, voiceDialog, attach
5925
5972
  ...stepPhrases && { stepPhrases },
5926
5973
  ...noticeLines && { noticeLines }
5927
5974
  }),
5928
- [widgets, diagrams, liveDemos, imageClients, videos, appEvents, workView, stepPhrases, noticeLines]
5975
+ [widgets, diagrams, liveDemos, imageClients, videos, appEvents, workView, stepPhrases, noticeLines, showReasoning, showAnswerDetails, answerModelNames]
5929
5976
  );
5930
5977
  const scrollRef = useRef17(null);
5931
5978
  const feedRef = useRef17(null);
@@ -6301,6 +6348,7 @@ ${text}` : text),
6301
6348
  composer: /* @__PURE__ */ jsx9(
6302
6349
  skin.Composer,
6303
6350
  {
6351
+ options: effectiveComposerOptions,
6304
6352
  value: chat.value,
6305
6353
  onValueChange: chat.setValue,
6306
6354
  onSubmit: submitComposer,
@@ -6496,6 +6544,7 @@ var AgentAnswer = memo4(function AgentAnswer2({
6496
6544
  const imagesView = isToolUIPart3(part) ? describeImages?.(part) : void 0;
6497
6545
  const videoView = isToolUIPart3(part) && extensions.videos && getToolName5(part) === extensions.videos.toolName ? extensions.videos.describe(part) : void 0;
6498
6546
  if (part.type === "reasoning") {
6547
+ if (extensions.reasoningVisible === false) return;
6499
6548
  const ordinal = String(reasoningIndex++);
6500
6549
  const streaming = part.state === "streaming";
6501
6550
  const durationMs = streaming ? null : durations[ordinal] ?? null;
@@ -6631,9 +6680,12 @@ var AgentAnswer = memo4(function AgentAnswer2({
6631
6680
  }));
6632
6681
  const errorText = status === "error" ? customErrorText(labels, message.metadata?.error) : void 0;
6633
6682
  const notice = status === "error" ? /* @__PURE__ */ jsx9(skin.ErrorNotice, { kind: message.metadata?.error?.kind ?? "other", ...errorText !== void 0 && { text: errorText }, ...onRetry && { onRetry } }) : void 0;
6683
+ const answerProvider = extensions.modelNames?.find((provider) => provider.id === message.metadata?.providerId);
6684
+ const answerModel = answerProvider?.models.find((model) => model.id === message.metadata?.modelId);
6634
6685
  return /* @__PURE__ */ jsx9(ThinkingClockProvider, { id: message.id, waiting: status === "streaming", children: /* @__PURE__ */ jsxs(
6635
6686
  skin.AgentMessage,
6636
6687
  {
6688
+ ...extensions.answerDetails && message.metadata?.modelId && message.metadata?.providerId && { modelDetails: { modelId: message.metadata.modelId, providerId: message.metadata.providerId, ...answerProvider && { providerName: answerProvider.name }, ...answerModel && { modelName: answerModel.name }, ...message.metadata.reasoningLevel !== void 0 && { reasoning: message.metadata.reasoningLevel } } },
6637
6689
  status,
6638
6690
  empty: !steered && status !== "streaming" && status !== "awaiting_approval" && text.trim().length === 0,
6639
6691
  onCopy: () => void navigator.clipboard?.writeText(text),
@@ -7172,4 +7224,4 @@ export {
7172
7224
  activeChatTitle,
7173
7225
  useWorkspaceListSync
7174
7226
  };
7175
- //# sourceMappingURL=chunk-Z7X5D4OF.js.map
7227
+ //# sourceMappingURL=chunk-WJJY2WLQ.js.map