@ykurilov/agent-chat-skin-tandem 0.68.0 → 0.69.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,33 @@
1
1
  # @ykurilov/agent-chat-skin-tandem
2
2
 
3
+ ## 0.69.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 857777b: Ряд значков шагов в Тандеме — по настоящей ширине строки, «+N» всегда помещается, не вылезает за край узкой колонки; модели заголовков раздумий уходит не больше 1200 знаков раздумья (начало и конец) — длинный заголовок вдвое дешевле.
8
+ - @ykurilov/agent-chat-flows@0.69.1
9
+ - @ykurilov/agent-chat-mcp@0.69.1
10
+ - @ykurilov/agent-chat-messengers@0.69.1
11
+ - @ykurilov/agent-chat-sandbox@0.69.1
12
+ - @ykurilov/agent-chat-skills@0.69.1
13
+ - @ykurilov/agent-chat-react@0.69.1
14
+
15
+ ## 0.69.0
16
+
17
+ ### Minor Changes
18
+
19
+ - e6c03dc: Значки шагов и заголовки раздумий (106): в простом виде — ряд значков у «Сделано N шагов» и список «значок + фраза» (`StepIcon`, `icon` в `stepPhrases`, `guessStepIcon`); `thoughtTitles` у службы чата — заголовок раздумья от провайдера или пересказ дешёвой моделью в фоне, хранится в части раздумья, в запрос основной модели не идёт, расход `thought_title`; договор скина 28 (`WorkStepsView.live.icon`, `done.items`, `icons`, `moreIcons`, `Reasoning.title`); фраза навыка с именем.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [e6c03dc]
24
+ - @ykurilov/agent-chat-react@0.69.0
25
+ - @ykurilov/agent-chat-skills@0.69.0
26
+ - @ykurilov/agent-chat-flows@0.69.0
27
+ - @ykurilov/agent-chat-mcp@0.69.0
28
+ - @ykurilov/agent-chat-messengers@0.69.0
29
+ - @ykurilov/agent-chat-sandbox@0.69.0
30
+
3
31
  ## 0.68.0
4
32
 
5
33
  ### Patch Changes
package/README.md CHANGED
@@ -29,6 +29,10 @@ pnpm add @ykurilov/agent-chat-skin-tandem "@tandem/ui@npm:@ykurilov/tandem-ui@^0
29
29
  (`BranchSwitcher`), правка идёт на месте пузыря (`MessageEditor`). Строка с кнопками занимает прежний промежуток между сообщениями:
30
30
  высота сообщения не меняется. На телефоне кнопки видны без наведения.
31
31
 
32
+ Простой вид работы агента (договор 28): рядом с «Сделано N шагов» — ряд значков шагов lucide (больше 8 — «+N»), в
33
+ раскрытом списке у каждой строки свой значок, в живой строке значок текущего шага заменяет точку. Раздумье с заголовком
34
+ от сервера показывает его вместо «Думал N сек», и в подробном виде тоже.
35
+
32
36
  Свои кнопки приложения под ответом (`messageActions`, договор скина версии 17) стоят в том же ряду, после копирования: с иконкой —
33
37
  кнопка-иконка с подсказкой, как копирование; без иконки — подпись текстом, как «Повторить». Пока обработчик идёт, кнопка неактивна.
34
38
 
package/dist/index.js CHANGED
@@ -1907,10 +1907,10 @@ import { useChatLabels as useChatLabels30 } from "@ykurilov/agent-chat-react";
1907
1907
  import { ChevronDown as ChevronDown3 } from "lucide-react";
1908
1908
  import { useId as useId3 } from "react";
1909
1909
  import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
1910
- function TandemReasoning({ text, streaming, durationMs, open, onOpenChange }) {
1910
+ function TandemReasoning({ text, streaming, durationMs, title: thoughtTitle, open, onOpenChange }) {
1911
1911
  const labels = useChatLabels30();
1912
1912
  const contentId = useId3();
1913
- const title = streaming ? /* @__PURE__ */ jsx34(TandemThinkingLabel, {}) : durationMs === null ? labels.reasoningRunning : labels.reasoningDone(Math.max(1, Math.round(durationMs / 1e3)));
1913
+ const title = streaming ? /* @__PURE__ */ jsx34(TandemThinkingLabel, {}) : thoughtTitle ? thoughtTitle : durationMs === null ? labels.reasoningRunning : labels.reasoningDone(Math.max(1, Math.round(durationMs / 1e3)));
1914
1914
  return /* @__PURE__ */ jsxs31("div", { className: "mb-2 not-first:mt-3", children: [
1915
1915
  /* @__PURE__ */ jsxs31(
1916
1916
  Button20,
@@ -2775,23 +2775,99 @@ function StatusMark2({ status }) {
2775
2775
  }
2776
2776
 
2777
2777
  // src/work-steps.tsx
2778
- import { Button as Button29, cn as cn12 } from "@tandem/ui";
2778
+ import { Button as Button29, cn as cn13 } from "@tandem/ui";
2779
2779
  import { useChatLabels as useChatLabels43 } from "@ykurilov/agent-chat-react";
2780
2780
  import { ChevronDown as ChevronDown6 } from "lucide-react";
2781
- import { useId as useId13 } from "react";
2782
- import { jsx as jsx51, jsxs as jsxs42 } from "react/jsx-runtime";
2781
+ import { useId as useId13, useLayoutEffect as useLayoutEffect4, useRef as useRef12, useState as useState8 } from "react";
2782
+
2783
+ // src/step-icon.tsx
2784
+ import { cn as cn12 } from "@tandem/ui";
2785
+ import { BookOpen, Bookmark, Bot, Brain as Brain2, Camera, CircleCheck, Clock as Clock2, Eye, Film as Film2, Globe, Image, Pencil as Pencil4, Plug, Search, SquareTerminal, Wrench } from "lucide-react";
2786
+ import { jsx as jsx51 } from "react/jsx-runtime";
2787
+ var ICONS = {
2788
+ thinking: Brain2,
2789
+ read: Eye,
2790
+ edit: Pencil4,
2791
+ search: Search,
2792
+ web: Globe,
2793
+ skill: BookOpen,
2794
+ mcp: Plug,
2795
+ image: Image,
2796
+ video: Film2,
2797
+ check: CircleCheck,
2798
+ snapshot: Camera,
2799
+ run: SquareTerminal,
2800
+ memory: Bookmark,
2801
+ helper: Bot,
2802
+ task: Clock2,
2803
+ tool: Wrench
2804
+ };
2805
+ function TandemStepIcon({ icon, className }) {
2806
+ const known = Object.hasOwn(ICONS, icon) ? icon : "tool";
2807
+ const Icon = ICONS[known];
2808
+ return /* @__PURE__ */ jsx51(Icon, { "aria-hidden": "true", "data-step-icon": known, className: cn12("size-3.5 shrink-0", className), strokeWidth: 1.75 });
2809
+ }
2810
+
2811
+ // src/work-steps.tsx
2812
+ import { jsx as jsx52, jsxs as jsxs42 } from "react/jsx-runtime";
2813
+ var GAP_PX = 4;
2814
+ function visibleStepIcons(available, total, plusWidthOf, iconPx = 14) {
2815
+ const iconsWidth = (count) => count * iconPx + Math.max(0, count - 1) * GAP_PX;
2816
+ if (iconsWidth(total) <= available) return total;
2817
+ for (let count = total - 1; count > 0; count--) if (iconsWidth(count) + GAP_PX + plusWidthOf(total - count) <= available) return count;
2818
+ return plusWidthOf(total) <= available ? 0 : null;
2819
+ }
2820
+ function useIconRowSpace() {
2821
+ const lineRef = useRef12(null);
2822
+ const rowRef = useRef12(null);
2823
+ const [measured, setMeasured] = useState8(null);
2824
+ useLayoutEffect4(() => {
2825
+ const line = lineRef.current;
2826
+ if (!line || typeof ResizeObserver === "undefined") return;
2827
+ const measure = () => {
2828
+ const button = line.firstElementChild;
2829
+ const row = rowRef.current;
2830
+ if (!button || !row || line.clientWidth === 0) return;
2831
+ const rest = button.getBoundingClientRect().width - row.getBoundingClientRect().width;
2832
+ const icon = row.querySelector("svg")?.getBoundingClientRect().width;
2833
+ const more = row.querySelector("[data-step-icons-more]");
2834
+ const moreText = more?.textContent ?? "";
2835
+ setMeasured((previous) => {
2836
+ const next = {
2837
+ space: Math.max(0, line.clientWidth - rest - 4),
2838
+ iconPx: icon || previous?.iconPx || 16,
2839
+ // Знак «+N» с запасом в 1 px: «+N» всегда влезает.
2840
+ charPx: more && moreText ? more.getBoundingClientRect().width / moreText.length + 1 : previous?.charPx ?? 8
2841
+ };
2842
+ return previous && previous.space === next.space && previous.iconPx === next.iconPx && previous.charPx === next.charPx ? previous : next;
2843
+ });
2844
+ };
2845
+ measure();
2846
+ const observer = new ResizeObserver(measure);
2847
+ observer.observe(line);
2848
+ if (rowRef.current) observer.observe(rowRef.current);
2849
+ return () => observer.disconnect();
2850
+ }, []);
2851
+ return { lineRef, rowRef, measured };
2852
+ }
2783
2853
  function TandemWorkSteps(view) {
2784
2854
  const labels = useChatLabels43();
2785
2855
  const listId = useId13();
2856
+ const { lineRef, rowRef, measured } = useIconRowSpace();
2786
2857
  if (view.state === "live") {
2787
2858
  return /* @__PURE__ */ jsxs42("div", { "data-work-live": true, className: "mb-2 flex h-7 min-w-0 items-center gap-2 last:mb-0 text-xs font-medium text-[var(--foreground-muted)] not-first:mt-3", children: [
2788
- /* @__PURE__ */ jsx51("span", { "aria-hidden": "true", className: "size-1.5 shrink-0 rounded-full bg-[var(--action)] motion-safe:animate-pulse" }),
2789
- /* @__PURE__ */ jsx51("span", { "aria-hidden": "true", "data-work-phrase": true, className: "min-w-0 truncate motion-safe:animate-[tandem-fade-in_var(--motion-fast)_ease-out]", children: view.phrase }, view.phrase),
2790
- /* @__PURE__ */ jsx51("span", { role: "status", className: "sr-only", children: labels.workView.live(labels.workView.stepRunning) })
2859
+ view.icon ? /* @__PURE__ */ jsx52(TandemStepIcon, { icon: view.icon, className: "text-[var(--action)] motion-safe:animate-pulse" }) : /* @__PURE__ */ jsx52("span", { "aria-hidden": "true", className: "size-1.5 shrink-0 rounded-full bg-[var(--action)] motion-safe:animate-pulse" }),
2860
+ /* @__PURE__ */ jsx52("span", { "aria-hidden": "true", "data-work-phrase": true, className: "min-w-0 truncate motion-safe:animate-[tandem-fade-in_var(--motion-fast)_ease-out]", children: view.phrase }, view.phrase),
2861
+ /* @__PURE__ */ jsx52("span", { role: "status", className: "sr-only", children: labels.workView.live(labels.workView.stepRunning) })
2791
2862
  ] });
2792
2863
  }
2793
- const { steps, count, open, onOpenChange } = view;
2794
- return /* @__PURE__ */ jsxs42("div", { className: "mb-2 not-first:mt-3", children: [
2864
+ const { steps, count, open, onOpenChange, items, icons = [], moreIcons = 0 } = view;
2865
+ const list = items ?? steps.map((step) => ({ ...step, icon: void 0 }));
2866
+ const total = icons.length + moreIcons;
2867
+ const fitting = measured && visibleStepIcons(measured.space, total, (count2) => labels.workView.moreSteps(count2).length * measured.charPx, measured.iconPx);
2868
+ const shown = measured === null ? icons.length : fitting === null ? 0 : Math.min(icons.length, fitting);
2869
+ const hidden = fitting === null && measured !== null ? 0 : total - shown;
2870
+ return /* @__PURE__ */ jsxs42("div", { ref: lineRef, className: "mb-2 min-w-0 not-first:mt-3", children: [
2795
2871
  /* @__PURE__ */ jsxs42(
2796
2872
  Button29,
2797
2873
  {
@@ -2800,14 +2876,30 @@ function TandemWorkSteps(view) {
2800
2876
  "aria-expanded": open,
2801
2877
  "aria-controls": listId,
2802
2878
  onClick: () => onOpenChange(!open),
2803
- className: "-ml-2 h-7 gap-1 px-2 text-xs font-medium text-[var(--foreground-muted)]",
2879
+ className: "-ml-2 h-7 max-w-full min-w-0 gap-1 px-2 text-xs font-medium text-[var(--foreground-muted)]",
2804
2880
  children: [
2805
- labels.workView.stepsDone(count),
2806
- /* @__PURE__ */ jsx51(ChevronDown6, { "aria-hidden": "true", className: cn12("size-3.5 transition-transform duration-[var(--motion-fast)]", open && "rotate-180") })
2881
+ /* @__PURE__ */ jsx52("span", { className: "shrink-0", children: labels.workView.stepsDone(count) }),
2882
+ icons.length > 0 && /* @__PURE__ */ jsxs42("span", { ref: rowRef, "data-step-icons": true, "aria-hidden": "true", className: "ml-1 flex min-w-0 items-center gap-1 overflow-hidden text-[var(--foreground-subtle)]", children: [
2883
+ icons.slice(0, shown).map((icon, index) => /* @__PURE__ */ jsx52(TandemStepIcon, { icon }, index)),
2884
+ hidden > 0 && /* @__PURE__ */ jsx52("span", { "data-step-icons-more": true, className: "shrink-0 tabular-nums", children: labels.workView.moreSteps(hidden) })
2885
+ ] }),
2886
+ /* @__PURE__ */ jsx52(ChevronDown6, { "aria-hidden": "true", className: cn13("size-3.5 shrink-0 transition-transform duration-[var(--motion-fast)]", open && "rotate-180") })
2807
2887
  ]
2808
2888
  }
2809
2889
  ),
2810
- /* @__PURE__ */ jsx51("ol", { id: listId, hidden: !open, className: "mt-1 space-y-1 border-l-2 border-[var(--border)] pl-3 text-xs leading-5 text-[var(--foreground-muted)]", children: open && steps.map((step, index) => /* @__PURE__ */ jsx51("li", { "data-work-step": true, "data-failed": String(step.failed), className: cn12("min-w-0 break-words [overflow-wrap:anywhere]", step.failed && "text-[var(--foreground-subtle)] line-through decoration-[var(--border)]"), children: step.phrase }, index)) })
2890
+ /* @__PURE__ */ jsx52("ol", { id: listId, hidden: !open, className: "mt-1 space-y-1 border-l-2 border-[var(--border)] pl-3 text-xs leading-5 text-[var(--foreground-muted)]", children: open && list.map((step, index) => /* @__PURE__ */ jsxs42(
2891
+ "li",
2892
+ {
2893
+ "data-work-step": true,
2894
+ "data-failed": String(step.failed),
2895
+ className: cn13("flex min-w-0 items-start gap-1.5 break-words [overflow-wrap:anywhere]", step.failed && "text-[var(--foreground-subtle)] line-through decoration-[var(--border)]"),
2896
+ children: [
2897
+ step.icon && /* @__PURE__ */ jsx52(TandemStepIcon, { icon: step.icon, className: "mt-[3px] text-[var(--foreground-subtle)]" }),
2898
+ /* @__PURE__ */ jsx52("span", { className: "min-w-0", children: step.phrase })
2899
+ ]
2900
+ },
2901
+ index
2902
+ )) })
2811
2903
  ] });
2812
2904
  }
2813
2905