@neuronection/assistant-ui 0.52.0 → 0.53.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +4 -1
  3. package/dist/chat-branch-tree.d.ts +15 -2
  4. package/dist/chat-composer.d.ts +43 -3
  5. package/dist/chat-composer.js +1 -1
  6. package/dist/chat-core.d.ts +54 -4
  7. package/dist/chat-launcher.js +1 -1
  8. package/dist/chat-markdown.d.ts +8 -1
  9. package/dist/chat-markdown.js +1 -1
  10. package/dist/chat-message.d.ts +16 -1
  11. package/dist/chat-message.js +1 -1
  12. package/dist/chat-panel.js +1 -1
  13. package/dist/chat-pending.d.ts +25 -0
  14. package/dist/chat-pending.js +3 -0
  15. package/dist/chat-pending.js.map +1 -0
  16. package/dist/chat-reasoning.d.ts +10 -2
  17. package/dist/chat-reasoning.js +1 -1
  18. package/dist/chat-session-list.d.ts +7 -0
  19. package/dist/chat-session-list.js +1 -1
  20. package/dist/chat-suggestion-card.d.ts +19 -0
  21. package/dist/chat-suggestion-card.js +3 -0
  22. package/dist/chat-suggestion-card.js.map +1 -0
  23. package/dist/chat-tool-card.js +1 -1
  24. package/dist/chat-transcript.d.ts +81 -8
  25. package/dist/chat-transcript.js +1 -1
  26. package/dist/chat-turn-status.js +1 -1
  27. package/dist/chat-welcome.d.ts +23 -0
  28. package/dist/chat-welcome.js +3 -0
  29. package/dist/chat-welcome.js.map +1 -0
  30. package/dist/{chunk-PC3CE4W2.js → chunk-22MIIWC7.js} +45 -7
  31. package/dist/chunk-22MIIWC7.js.map +1 -0
  32. package/dist/chunk-56QSIK4Y.js +264 -0
  33. package/dist/chunk-56QSIK4Y.js.map +1 -0
  34. package/dist/{chunk-GDGKARRB.js → chunk-5C2OLUDR.js} +11 -2
  35. package/dist/chunk-5C2OLUDR.js.map +1 -0
  36. package/dist/chunk-E2NNTHFB.js +479 -0
  37. package/dist/chunk-E2NNTHFB.js.map +1 -0
  38. package/dist/{chunk-5BJ4VULP.js → chunk-EQPXM2R4.js} +23 -10
  39. package/dist/chunk-EQPXM2R4.js.map +1 -0
  40. package/dist/chunk-F4BTJQLK.js +43 -0
  41. package/dist/chunk-F4BTJQLK.js.map +1 -0
  42. package/dist/{chunk-TQDTFPM4.js → chunk-GIQZNEG5.js} +3 -3
  43. package/dist/chunk-GIQZNEG5.js.map +1 -0
  44. package/dist/{chunk-E3NZM4U7.js → chunk-GO4ILLS6.js} +17 -4
  45. package/dist/chunk-GO4ILLS6.js.map +1 -0
  46. package/dist/{chunk-5U45TQJE.js → chunk-I5H4KYEZ.js} +3 -3
  47. package/dist/{chunk-5U45TQJE.js.map → chunk-I5H4KYEZ.js.map} +1 -1
  48. package/dist/{chunk-S63SYV6O.js → chunk-INXQJM2K.js} +27 -6
  49. package/dist/chunk-INXQJM2K.js.map +1 -0
  50. package/dist/{chunk-HQ3X5SVR.js → chunk-KLCULTOQ.js} +105 -16
  51. package/dist/chunk-KLCULTOQ.js.map +1 -0
  52. package/dist/chunk-M4SYVPC4.js +3 -0
  53. package/dist/chunk-M4SYVPC4.js.map +1 -0
  54. package/dist/chunk-OOQO6VGO.js +33 -0
  55. package/dist/chunk-OOQO6VGO.js.map +1 -0
  56. package/dist/chunk-P3QUNCBR.js +366 -0
  57. package/dist/chunk-P3QUNCBR.js.map +1 -0
  58. package/dist/chunk-PABMR4RA.js +30 -0
  59. package/dist/chunk-PABMR4RA.js.map +1 -0
  60. package/dist/{chunk-SEZZVAGP.js → chunk-SR4IN564.js} +10 -3
  61. package/dist/chunk-SR4IN564.js.map +1 -0
  62. package/dist/{chunk-MFM4W6ZO.js → chunk-TLFEVL42.js} +3 -3
  63. package/dist/{chunk-MFM4W6ZO.js.map → chunk-TLFEVL42.js.map} +1 -1
  64. package/dist/index.d.ts +243 -36
  65. package/dist/index.js +40 -37
  66. package/dist/markdown-diff-view.js +1 -1
  67. package/dist/model-registry.js +1 -1
  68. package/dist/styles.css +1 -1
  69. package/dist/theme-scope.d.ts +1 -1
  70. package/dist/tokens.d.ts +1 -1
  71. package/dist/tokens.js +1 -1
  72. package/dist/{types-LhE1KFBf.d.ts → types-CxieoN2u.d.ts} +1 -1
  73. package/dist/types-pivJdPC8.d.ts +14 -0
  74. package/package.json +13 -1
  75. package/dist/chunk-5BJ4VULP.js.map +0 -1
  76. package/dist/chunk-C5JZCCU7.js +0 -220
  77. package/dist/chunk-C5JZCCU7.js.map +0 -1
  78. package/dist/chunk-E3NZM4U7.js.map +0 -1
  79. package/dist/chunk-GDGKARRB.js.map +0 -1
  80. package/dist/chunk-HQ3X5SVR.js.map +0 -1
  81. package/dist/chunk-PC3CE4W2.js.map +0 -1
  82. package/dist/chunk-S63SYV6O.js.map +0 -1
  83. package/dist/chunk-SEZZVAGP.js.map +0 -1
  84. package/dist/chunk-TQDTFPM4.js.map +0 -1
  85. package/dist/chunk-XOODPYJ3.js +0 -173
  86. package/dist/chunk-XOODPYJ3.js.map +0 -1
  87. package/dist/chunk-YQQZMWHL.js +0 -201
  88. package/dist/chunk-YQQZMWHL.js.map +0 -1
  89. package/dist/tree-4Hv9stjF.d.ts +0 -54
@@ -1,17 +1,60 @@
1
1
  import * as React from 'react';
2
- import { d as ChatMessageView } from './types-LhE1KFBf.js';
2
+ import { e as ChatMessageView } from './types-CxieoN2u.js';
3
+
4
+ interface ChatSearchFieldLabels {
5
+ search: string;
6
+ placeholder: string;
7
+ previous: string;
8
+ next: string;
9
+ clear: string;
10
+ matches: string;
11
+ }
12
+ interface ChatSearchFieldProps {
13
+ query: string;
14
+ onQueryChange: (query: string) => void;
15
+ /** Total matches in the transcript (drives the `n/N` readout). */
16
+ matchCount?: number;
17
+ /** 0-based active match index. */
18
+ activeIndex?: number;
19
+ onPrevious?: () => void;
20
+ onNext?: () => void;
21
+ labels?: Partial<ChatSearchFieldLabels>;
22
+ className?: string;
23
+ }
24
+ /**
25
+ * The in-conversation search field (plan 101 P2): controlled query input
26
+ * with an `n/N` match readout and prev/next controls. Enter jumps to the
27
+ * next match, Shift+Enter to the previous, Escape clears. Composed by
28
+ * `ChatTranscript` when its `search` prop is given, or standalone.
29
+ */
30
+ declare const ChatSearchField: React.ForwardRefExoticComponent<ChatSearchFieldProps & React.RefAttributes<HTMLDivElement>>;
3
31
 
4
32
  interface ChatTranscriptLabels {
5
33
  log: string;
6
34
  scrollToBottom: string;
35
+ newMessages: string;
7
36
  empty: string;
8
37
  replied: string;
38
+ search: string;
39
+ searchPlaceholder: string;
40
+ previousMatch: string;
41
+ nextMatch: string;
42
+ clearSearch: string;
43
+ matches: string;
44
+ today: string;
45
+ yesterday: string;
46
+ stop: string;
47
+ }
48
+ type ChatTranscriptGroupPos = 'single' | 'start' | 'mid' | 'end';
49
+ interface ChatTranscriptSearch {
50
+ query: string;
51
+ onQueryChange: (query: string) => void;
9
52
  }
10
53
  interface ChatTranscriptProps {
11
54
  items: ChatMessageView[];
12
- /** Override the default `ChatMessage` + `MarkdownSurface` rendering. */
13
- renderItem?: (message: ChatMessageView, index: number) => React.ReactNode;
14
- /** Streaming tail — the live turn bubble (or any node). */
55
+ /** Override the default `ChatMessage` + `MarkdownSurface` rendering (third arg: group position when `groupConsecutive`; fourth arg: active search query). */
56
+ renderItem?: (message: ChatMessageView, index: number, groupPos?: ChatTranscriptGroupPos, query?: string) => React.ReactNode;
57
+ /** Streaming tail — the live turn bubble (or `ChatPending`) or any node. */
15
58
  live?: React.ReactNode;
16
59
  emptyState?: React.ReactNode;
17
60
  /** Stick to bottom while the user hasn't scrolled away. Default true. */
@@ -20,14 +63,44 @@ interface ChatTranscriptProps {
20
63
  scrollThresholdPx?: number;
21
64
  /** Virtualize long transcripts (dynamic row measurement). Default false. */
22
65
  virtualized?: boolean;
23
- labels?: Partial<ChatTranscriptLabels>;
66
+ /**
67
+ * Tighten the spacing between consecutive same-role messages and tag
68
+ * each row with `data-group-pos` (`single | start | mid | end`).
69
+ * Default false.
70
+ */
71
+ groupConsecutive?: boolean;
72
+ /** Visual design for the default renderer: paired bubbles (default) or flat assistant prose over the panel surface. */
73
+ design?: 'bubbles' | 'flat';
74
+ /**
75
+ * In-conversation search (plan 101 P2): renders `ChatSearchField`
76
+ * above the log, highlights matches in the default renderer, and
77
+ * jumps between them (Enter/Shift+Enter in the field, or the
78
+ * prev/next buttons). Jump-scrolling requires a non-virtualized
79
+ * transcript; virtualized transcripts highlight visible rows only.
80
+ */
81
+ search?: ChatTranscriptSearch;
82
+ /**
83
+ * Today/Yesterday/date chips between day groups (reads
84
+ * `ChatMessageView.createdAt`; rows without one join the previous
85
+ * group). Default false.
86
+ */
87
+ showDateSeparators?: boolean;
88
+ /** Custom formatter for older-day chips (default `MMM d, yyyy`). */
89
+ formatDate?: (date: Date) => string;
90
+ /** Scroll a message into view and flash it when this changes (branch-tree clicks, citations, deep links). */
91
+ focusMessageId?: string | null;
92
+ /** Show a floating stop pill beside the jump pill while a live turn streams and the user scrolled away. */
93
+ onStop?: () => void;
94
+ labels?: Partial<ChatTranscriptLabels & ChatSearchFieldLabels>;
24
95
  className?: string;
25
96
  }
26
97
  /**
27
98
  * The conversation list: `role="log"` with polite turn-completion
28
- * announcements, stick-to-bottom auto-scroll with a jump-to-latest pill,
29
- * optional virtualization for thousand-message threads.
99
+ * announcements, stick-to-bottom auto-scroll with an unread-counting
100
+ * jump-to-latest pill, optional consecutive-message grouping, a top
101
+ * fade mask while scrolled into the thread, thin themed scrollbars,
102
+ * and optional virtualization for thousand-message threads.
30
103
  */
31
104
  declare const ChatTranscript: React.ForwardRefExoticComponent<ChatTranscriptProps & React.RefAttributes<HTMLDivElement>>;
32
105
 
33
- export { ChatTranscript, type ChatTranscriptLabels, type ChatTranscriptProps };
106
+ export { ChatSearchField, type ChatSearchFieldLabels, type ChatSearchFieldProps, ChatTranscript, type ChatTranscriptGroupPos, type ChatTranscriptLabels, type ChatTranscriptProps, type ChatTranscriptSearch };
@@ -1,3 +1,3 @@
1
- export { ChatTranscript } from './chunk-XOODPYJ3.js';
1
+ export { ChatSearchField, ChatTranscript } from './chunk-E2NNTHFB.js';
2
2
  //# sourceMappingURL=chat-transcript.js.map
3
3
  //# sourceMappingURL=chat-transcript.js.map
@@ -1,3 +1,3 @@
1
- export { ChatTurnStatus } from './chunk-TQDTFPM4.js';
1
+ export { ChatTurnStatus } from './chunk-GIQZNEG5.js';
2
2
  //# sourceMappingURL=chat-turn-status.js.map
3
3
  //# sourceMappingURL=chat-turn-status.js.map
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+
3
+ interface ChatWelcomeProps {
4
+ /** Greeting headline, e.g. "Ask anything about your courses". */
5
+ title: string;
6
+ subtitle?: string;
7
+ /** Suggestion cards / chips — children stagger in (40 ms steps). */
8
+ suggestions?: React.ReactNode;
9
+ /** Node inside the breathing `--as-ai` orb. Default: `Sparkles`. */
10
+ icon?: React.ReactNode;
11
+ /** `page` centers generously; `sidebar` is the default density; `bubble` is compact. */
12
+ variant?: 'page' | 'sidebar' | 'bubble';
13
+ className?: string;
14
+ }
15
+ /**
16
+ * The chat empty-state kit (plan 100 S6): breathing `--as-ai` identity
17
+ * orb, greeting, subtitle and staggered suggestion slots — drop it into
18
+ * `ChatTranscript emptyState`. Presentational; suggestion targets stay
19
+ * app-side.
20
+ */
21
+ declare const ChatWelcome: React.ForwardRefExoticComponent<ChatWelcomeProps & React.RefAttributes<HTMLDivElement>>;
22
+
23
+ export { ChatWelcome, type ChatWelcomeProps };
@@ -0,0 +1,3 @@
1
+ export { ChatWelcome } from './chunk-OOQO6VGO.js';
2
+ //# sourceMappingURL=chat-welcome.js.map
3
+ //# sourceMappingURL=chat-welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chat-welcome.js"}
@@ -36,6 +36,7 @@ var ChatSessionList = React.forwardRef(
36
36
  onSelect,
37
37
  onNew,
38
38
  onRename,
39
+ onRenameCommit,
39
40
  onDelete,
40
41
  onExport,
41
42
  searchable = true,
@@ -45,6 +46,8 @@ var ChatSessionList = React.forwardRef(
45
46
  className
46
47
  }, ref) {
47
48
  const [query, setQuery] = React.useState("");
49
+ const [editingId, setEditingId] = React.useState(null);
50
+ const [draftTitle, setDraftTitle] = React.useState("");
48
51
  const NewIcon = icons?.newChat ?? MessageSquarePlus;
49
52
  const RenameIcon = icons?.rename ?? Pencil;
50
53
  const DeleteIcon = icons?.delete ?? Trash2;
@@ -111,17 +114,31 @@ var ChatSessionList = React.forwardRef(
111
114
  }
112
115
  ) : null
113
116
  ] }),
114
- sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.empty ?? "No conversations yet" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.noResults ?? "No matches" }) : /* @__PURE__ */ jsx("div", { role: "list", "aria-label": labels?.search ?? "Conversations", className: "min-h-0 flex-1 overflow-y-auto", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
117
+ sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.empty ?? "No conversations yet" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-[var(--as-muted-fg)]", children: labels?.noResults ?? "No matches" }) : /* @__PURE__ */ jsx("div", { role: "list", "aria-label": labels?.search ?? "Conversations", "data-as": "chat-session-rows", className: "min-h-0 flex-1 overflow-y-auto", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-2", children: [
115
118
  group.label ? /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]", children: group.label }) : null,
116
119
  group.sessions.map((session) => {
117
120
  const isActive = session.id === activeId;
118
121
  const date = toDate(session.updatedAt);
119
- const actions = onRename || onDelete || onExport ? [
120
- onRename ? {
122
+ const isEditing = editingId === session.id;
123
+ const commitRename = () => {
124
+ if (editingId !== null && draftTitle.trim() !== "") {
125
+ onRenameCommit?.(editingId, draftTitle.trim());
126
+ }
127
+ setEditingId(null);
128
+ };
129
+ const actions = onRename || onRenameCommit || onDelete || onExport ? [
130
+ onRename || onRenameCommit ? {
121
131
  key: "rename",
122
132
  label: labels?.rename ?? "Rename",
123
133
  icon: /* @__PURE__ */ jsx(RenameIcon, { className: "size-3.5", "aria-hidden": true }),
124
- onSelect: () => onRename(session.id)
134
+ onSelect: () => {
135
+ if (onRenameCommit !== void 0) {
136
+ setEditingId(session.id);
137
+ setDraftTitle(session.title);
138
+ } else {
139
+ onRename?.(session.id);
140
+ }
141
+ }
125
142
  } : null,
126
143
  onExport ? {
127
144
  key: "export",
@@ -156,7 +173,28 @@ var ChatSessionList = React.forwardRef(
156
173
  },
157
174
  tabIndex: 0,
158
175
  children: [
159
- /* @__PURE__ */ jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]", children: session.title }),
176
+ isEditing ? /* @__PURE__ */ jsx(
177
+ "input",
178
+ {
179
+ autoFocus: true,
180
+ value: draftTitle,
181
+ "aria-label": labels?.renameInput ?? "Conversation title",
182
+ onChange: (event) => setDraftTitle(event.target.value),
183
+ onClick: (event) => event.stopPropagation(),
184
+ onKeyDown: (event) => {
185
+ event.stopPropagation();
186
+ if (event.key === "Enter") {
187
+ event.preventDefault();
188
+ commitRename();
189
+ } else if (event.key === "Escape") {
190
+ event.preventDefault();
191
+ setEditingId(null);
192
+ }
193
+ },
194
+ onBlur: commitRename,
195
+ className: "min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-primary)] bg-[var(--as-surface)] px-1.5 py-0.5 text-xs text-[var(--as-fg)] focus-visible:outline-none"
196
+ }
197
+ ) : /* @__PURE__ */ jsx("span", { className: "line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]", children: session.title }),
160
198
  date ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-[var(--as-muted-fg)]", children: formatDistanceToNow(date, { addSuffix: true }) }) : null,
161
199
  actions.length > 0 ? /* @__PURE__ */ jsx(
162
200
  "span",
@@ -194,5 +232,5 @@ var ChatSessionList = React.forwardRef(
194
232
  );
195
233
 
196
234
  export { ChatSessionList };
197
- //# sourceMappingURL=chunk-PC3CE4W2.js.map
198
- //# sourceMappingURL=chunk-PC3CE4W2.js.map
235
+ //# sourceMappingURL=chunk-22MIIWC7.js.map
236
+ //# sourceMappingURL=chunk-22MIIWC7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-session-list/ChatSessionList.tsx"],"names":["ChatSessionList"],"mappings":";;;;;;;AA0DA,SAAS,OAAO,KAAA,EAA6D;AAC3E,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,IAAI,KAAK,KAAK,CAAA;AAAA,EACvB;AACA,EAAA,OAAO,SAAS,KAAK,CAAA;AACvB;AAEA,SAAS,SAAS,IAAA,EAAoE;AACpF,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAA,CAAQ,IAAI,CAAA,EAAG;AACjB,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,IAAI,WAAA,CAAY,IAAI,CAAA,EAAG;AACrB,IAAA,OAAO,WAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA;AACT;AAQO,IAAM,eAAA,GAAwB,KAAA,CAAA,UAAA;AAAA,EACnC,SAASA,gBAAAA,CACP;AAAA,IACE,QAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA,GAAa,IAAA;AAAA,IACb,WAAA,GAAc,IAAA;AAAA,IACd,MAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAU,eAAS,EAAE,CAAA;AAC3C,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAU,eAAwB,IAAI,CAAA;AACpE,IAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAU,eAAS,EAAE,CAAA;AACrD,IAAA,MAAM,OAAA,GAAU,OAAO,OAAA,IAAW,iBAAA;AAClC,IAAA,MAAM,UAAA,GAAa,OAAO,MAAA,IAAU,MAAA;AACpC,IAAA,MAAM,UAAA,GAAa,OAAO,MAAA,IAAU,MAAA;AACpC,IAAA,MAAM,UAAA,GAAa,OAAO,MAAA,IAAU,QAAA;AAEpC,IAAA,MAAM,QAAA,GAAiB,cAAQ,MAAM;AACnC,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,MAAA,IAAI,YAAY,EAAA,EAAI;AAClB,QAAA,OAAO,QAAA;AAAA,MACT;AACA,MAAA,OAAO,QAAA,CACJ,GAAA,CAAI,CAAC,OAAA,MAAa;AAAA,QACjB,OAAA;AAAA,QACA,KAAA,EAAO,WAAA,CAAY,OAAA,EAAS,OAAA,CAAQ,KAAK;AAAA,OAC3C,CAAE,CAAA,CACD,MAAA,CAAO,CAAC,KAAA,KAAU,MAAM,KAAA,KAAU,IAAI,CAAA,CACtC,IAAA,CAAK,CAAC,CAAA,EAAG,OAAO,CAAA,CAAE,KAAA,IAAS,CAAA,KAAM,CAAA,CAAE,KAAA,IAAS,CAAA,CAAE,EAC9C,GAAA,CAAI,CAAC,KAAA,KAAU,KAAA,CAAM,OAAO,CAAA;AAAA,IACjC,CAAA,EAAG,CAAC,QAAA,EAAU,KAAK,CAAC,CAAA;AAEpB,IAAA,MAAM,MAAA,GAAe,cAAQ,MAAM;AACjC,MAAA,IAAI,CAAC,WAAA,EAAa;AAChB,QAAA,OAAO,CAAC,EAAE,GAAA,EAAK,KAAA,EAAO,OAAO,IAAA,EAAuB,QAAA,EAAU,UAAU,CAAA;AAAA,MAC1E;AACA,MAAA,MAAM,OAAA,GAAU,CAAC,GAAG,QAAQ,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAC3C,QAAA,MAAM,QAAQ,MAAA,CAAO,CAAA,CAAE,SAAS,CAAA,EAAG,SAAQ,IAAK,CAAA;AAChD,QAAA,MAAM,QAAQ,MAAA,CAAO,CAAA,CAAE,SAAS,CAAA,EAAG,SAAQ,IAAK,CAAA;AAChD,QAAA,OAAO,KAAA,GAAQ,KAAA;AAAA,MACjB,CAAC,CAAA;AACD,MAAA,MAAM,QAAA,uBAAe,GAAA,EAA+B;AACpD,MAAA,KAAA,MAAW,WAAW,OAAA,EAAS;AAC7B,QAAA,MAAM,MAAA,GAAS,QAAA,CAAS,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAC,CAAA;AACjD,QAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,MAAM,KAAK,EAAC;AACtC,QAAA,IAAA,CAAK,KAAK,OAAO,CAAA;AACjB,QAAA,QAAA,CAAS,GAAA,CAAI,QAAQ,IAAI,CAAA;AAAA,MAC3B;AACA,MAAA,MAAM,KAAA,GAAQ,CAAC,OAAA,EAAS,WAAA,EAAa,WAAW,MAAM,CAAA;AACtD,MAAA,OAAO,KAAA,CACJ,MAAA,CAAO,CAAC,MAAA,KAAW,QAAA,CAAS,GAAA,CAAI,MAAM,CAAC,CAAA,CACvC,GAAA,CAAI,CAAC,MAAA,MAAY;AAAA,QAChB,GAAA,EAAK,MAAA;AAAA,QACL,KAAA,EACE,MAAA,KAAW,OAAA,GACN,MAAA,EAAQ,SAAS,OAAA,GAClB,MAAA,KAAW,WAAA,GACR,MAAA,EAAQ,aAAa,WAAA,GACtB,MAAA,KAAW,SAAA,GACR,MAAA,EAAQ,WAAW,SAAA,GACpB,IAAA;AAAA,QACV,QAAA,EAAU,QAAA,CAAS,GAAA,CAAI,MAAM,KAAK;AAAC,OACrC,CAAE,CAAA;AAAA,IACN,CAAA,EAAG,CAAC,QAAA,EAAU,WAAA,EAAa,MAAM,CAAC,CAAA;AAElC,IAAA,uBACE,IAAA,CAAC,SAAI,GAAA,EAAU,SAAA,EAAQ,qBAAoB,SAAA,EAAW,EAAA,CAAG,2CAAA,EAA6C,SAAS,CAAA,EAC7G,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,yBAAA,EACZ,QAAA,EAAA;AAAA,QAAA,UAAA,mBACC,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAO,SAAA,EAAU,kGAAA,EAAmG,aAAA,EAAW,IAAA,EAAC,CAAA;AAAA,0BACjI,GAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,IAAA,EAAK,WAAA;AAAA,cACL,KAAA,EAAO,KAAA;AAAA,cACP,YAAA,EAAY,QAAQ,MAAA,IAAU,sBAAA;AAAA,cAC9B,WAAA,EAAa,QAAQ,iBAAA,IAAqB,cAAA;AAAA,cAC1C,UAAU,CAAC,KAAA,KAAU,QAAA,CAAS,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA,cAChD,SAAA,EAAU;AAAA;AAAA;AACZ,SAAA,EACF,CAAA,GACE,IAAA;AAAA,QACH,KAAA,mBACC,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,OAAA,EAAS,KAAA;AAAA,YACT,YAAA,EAAY,QAAQ,OAAA,IAAW,kBAAA;AAAA,YAC/B,KAAA,EAAO,QAAQ,OAAA,IAAW,kBAAA;AAAA,YAC1B,SAAA,EAAU,8SAAA;AAAA,YAEV,QAAA,kBAAA,GAAA,CAAC,OAAA,EAAA,EAAQ,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA,SAC1C,GACE;AAAA,OAAA,EACN,CAAA;AAAA,MACC,SAAS,MAAA,KAAW,CAAA,uBAClB,GAAA,EAAA,EAAE,SAAA,EAAU,+CAA+C,QAAA,EAAA,MAAA,EAAQ,KAAA,IAAS,sBAAA,EAAuB,CAAA,GAClG,SAAS,MAAA,KAAW,CAAA,uBACrB,GAAA,EAAA,EAAE,SAAA,EAAU,+CAA+C,QAAA,EAAA,MAAA,EAAQ,SAAA,IAAa,YAAA,EAAa,CAAA,uBAE7F,KAAA,EAAA,EAAI,IAAA,EAAK,QAAO,YAAA,EAAY,MAAA,EAAQ,UAAU,eAAA,EAAiB,SAAA,EAAQ,qBAAoB,SAAA,EAAU,gCAAA,EACnG,iBAAO,GAAA,CAAI,CAAC,0BACX,IAAA,CAAC,KAAA,EAAA,EAAoB,WAAU,MAAA,EAC5B,QAAA,EAAA;AAAA,QAAA,KAAA,CAAM,wBACL,GAAA,CAAC,GAAA,EAAA,EAAE,WAAU,4FAAA,EACV,QAAA,EAAA,KAAA,CAAM,OACT,CAAA,GACE,IAAA;AAAA,QACH,KAAA,CAAM,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,KAAY;AAC/B,UAAA,MAAM,QAAA,GAAW,QAAQ,EAAA,KAAO,QAAA;AAChC,UAAA,MAAM,IAAA,GAAO,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA;AACrC,UAAA,MAAM,SAAA,GAAY,cAAc,OAAA,CAAQ,EAAA;AACxC,UAAA,MAAM,eAAe,MAAM;AACzB,YAAA,IAAI,SAAA,KAAc,IAAA,IAAQ,UAAA,CAAW,IAAA,OAAW,EAAA,EAAI;AAClD,cAAA,cAAA,GAAiB,SAAA,EAAW,UAAA,CAAW,IAAA,EAAM,CAAA;AAAA,YAC/C;AACA,YAAA,YAAA,CAAa,IAAI,CAAA;AAAA,UACnB,CAAA;AACA,UAAA,MAAM,OAAA,GACJ,QAAA,IAAY,cAAA,IAAkB,QAAA,IAAY,QAAA,GACtC;AAAA,YACE,YAAY,cAAA,GACR;AAAA,cACE,GAAA,EAAK,QAAA;AAAA,cACL,KAAA,EAAO,QAAQ,MAAA,IAAU,QAAA;AAAA,cACzB,sBAAM,GAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,UAAA,EAAW,eAAW,IAAA,EAAC,CAAA;AAAA,cACnD,UAAU,MAAM;AACd,gBAAA,IAAI,mBAAmB,MAAA,EAAW;AAChC,kBAAA,YAAA,CAAa,QAAQ,EAAE,CAAA;AACvB,kBAAA,aAAA,CAAc,QAAQ,KAAK,CAAA;AAAA,gBAC7B,CAAA,MAAO;AACL,kBAAA,QAAA,GAAW,QAAQ,EAAE,CAAA;AAAA,gBACvB;AAAA,cACF;AAAA,aACF,GACA,IAAA;AAAA,YACJ,QAAA,GACI;AAAA,cACE,GAAA,EAAK,QAAA;AAAA,cACL,KAAA,EAAO,QAAQ,MAAA,IAAU,QAAA;AAAA,cACzB,sBAAM,GAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,UAAA,EAAW,eAAW,IAAA,EAAC,CAAA;AAAA,cACnD,QAAA,EAAU,MAAM,QAAA,CAAS,OAAA,CAAQ,EAAE;AAAA,aACrC,GACA,IAAA;AAAA,YACJ,QAAA,GACI;AAAA,cACE,GAAA,EAAK,QAAA;AAAA,cACL,KAAA,EAAO,QAAQ,MAAA,IAAU,QAAA;AAAA,cACzB,sBAAM,GAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,UAAA,EAAW,eAAW,IAAA,EAAC,CAAA;AAAA,cACnD,QAAA,EAAU,MAAM,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAAA,cACnC,MAAA,EAAQ;AAAA,aACV,GACA;AAAA,YACJ,MAAA,CAAO,CAAC,SAA2C,IAAA,KAAS,IAAI,IAClE,EAAC;AACP,UAAA,uBACE,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cAEC,IAAA,EAAK,UAAA;AAAA,cACL,cAAA,EAAc,WAAW,MAAA,GAAS,MAAA;AAAA,cAClC,eAAa,QAAA,IAAY,MAAA;AAAA,cACzB,SAAA,EAAW,EAAA;AAAA,gBACT,2JAAA;AAAA,gBACA,QAAA,IAAY;AAAA,eACd;AAAA,cACA,OAAA,EAAS,MAAM,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAAA,cAClC,SAAA,EAAW,CAAC,KAAA,KAAU;AACpB,gBAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,OAAA,IAAW,KAAA,CAAM,QAAQ,GAAA,EAAK;AAC9C,kBAAA,KAAA,CAAM,cAAA,EAAe;AACrB,kBAAA,QAAA,CAAS,QAAQ,EAAE,CAAA;AAAA,gBACrB;AAAA,cACF,CAAA;AAAA,cACA,QAAA,EAAU,CAAA;AAAA,cAET,QAAA,EAAA;AAAA,gBAAA,SAAA,mBACC,GAAA;AAAA,kBAAC,OAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAS,IAAA;AAAA,oBACT,KAAA,EAAO,UAAA;AAAA,oBACP,YAAA,EAAY,QAAQ,WAAA,IAAe,oBAAA;AAAA,oBACnC,UAAU,CAAC,KAAA,KAAU,aAAA,CAAc,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA,oBACrD,OAAA,EAAS,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,EAAgB;AAAA,oBAC1C,SAAA,EAAW,CAAC,KAAA,KAAU;AACpB,sBAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,sBAAA,IAAI,KAAA,CAAM,QAAQ,OAAA,EAAS;AACzB,wBAAA,KAAA,CAAM,cAAA,EAAe;AACrB,wBAAA,YAAA,EAAa;AAAA,sBACf,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,QAAA,EAAU;AACjC,wBAAA,KAAA,CAAM,cAAA,EAAe;AACrB,wBAAA,YAAA,CAAa,IAAI,CAAA;AAAA,sBACnB;AAAA,oBACF,CAAA;AAAA,oBACA,MAAA,EAAQ,YAAA;AAAA,oBACR,SAAA,EAAU;AAAA;AAAA,oCAGZ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iDAAA,EAAmD,kBAAQ,KAAA,EAAM,CAAA;AAAA,gBAElF,IAAA,mBACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gDAAA,EACb,QAAA,EAAA,mBAAA,CAAoB,IAAA,EAAM,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA,EAChD,CAAA,GACE,IAAA;AAAA,gBACH,OAAA,CAAQ,SAAS,CAAA,mBAChB,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,OAAA;AAAA,oBACL,YAAA,EAAY,QAAQ,MAAA,IAAU,sBAAA;AAAA,oBAC9B,SAAA,EAAU,0HAAA;AAAA,oBACV,OAAA,EAAS,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,EAAgB;AAAA,oBAC1C,SAAA,EAAW,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,EAAgB;AAAA,oBAE3C,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,qBACZ,GAAA;AAAA,sBAAC,QAAA;AAAA,sBAAA;AAAA,wBAEC,IAAA,EAAK,QAAA;AAAA,wBACL,cAAY,MAAA,CAAO,KAAA;AAAA,wBACnB,OAAO,MAAA,CAAO,KAAA;AAAA,wBACd,SAAS,MAAA,CAAO,QAAA;AAAA,wBAChB,SAAA,EAAW,EAAA;AAAA,0BACT,oQAAA;AAAA,0BACA,OAAO,MAAA,IAAU;AAAA,yBACnB;AAAA,wBAEC,QAAA,EAAA,MAAA,CAAO;AAAA,uBAAA;AAAA,sBAVH,MAAA,CAAO;AAAA,qBAYf;AAAA;AAAA,iBACH,GACE;AAAA;AAAA,aAAA;AAAA,YArEC,OAAA,CAAQ;AAAA,WAsEd;AAAA,QAEJ,CAAC;AAAA,OAAA,EAAA,EA/HM,KAAA,CAAM,GAgId,CACD,CAAA,EAEL;AAAA,KAAA,EAEJ,CAAA;AAAA,EAEJ;AACF","file":"chunk-22MIIWC7.js","sourcesContent":["import * as React from 'react'\nimport { Download, MessageSquarePlus, Pencil, Search, Trash2 } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { formatDistanceToNow, isToday, isYesterday, parseISO } from 'date-fns'\n\nimport { cn } from '../../lib/utils'\nimport { searchScore } from '../../lib/fuzzy'\n\nexport interface ChatSessionView {\n id: string\n title: string\n updatedAt?: string | number | Date\n meta?: Record<string, unknown>\n}\n\nexport interface ChatSessionListLabels {\n search: string\n searchPlaceholder: string\n newChat: string\n rename: string\n renameInput: string\n delete: string\n export: string\n today: string\n yesterday: string\n earlier: string\n empty: string\n noResults: string\n}\n\nexport interface ChatSessionListIcons {\n newChat: LucideIcon\n rename: LucideIcon\n delete: LucideIcon\n export: LucideIcon\n}\n\nexport interface ChatSessionListProps {\n sessions: ChatSessionView[]\n activeId?: string | null\n onSelect: (id: string) => void\n onNew?: () => void\n onRename?: (id: string) => void\n /**\n * Inline rename: when provided, the rename action edits the title in\n * place (Enter/blur commits, Escape cancels) instead of invoking\n * `onRename` — keep `onRename` for dialog-based flows only.\n */\n onRenameCommit?: (id: string, title: string) => void\n onDelete?: (id: string) => void\n onExport?: (id: string) => void\n searchable?: boolean\n groupByDate?: boolean\n labels?: Partial<ChatSessionListLabels>\n icons?: Partial<ChatSessionListIcons>\n className?: string\n}\n\nfunction toDate(value: string | number | Date | undefined): Date | undefined {\n if (value === undefined) {\n return undefined\n }\n if (value instanceof Date) {\n return value\n }\n if (typeof value === 'number') {\n return new Date(value)\n }\n return parseISO(value)\n}\n\nfunction bucketOf(date: Date | undefined): 'today' | 'yesterday' | 'earlier' | 'none' {\n if (date === undefined) {\n return 'none'\n }\n if (isToday(date)) {\n return 'today'\n }\n if (isYesterday(date)) {\n return 'yesterday'\n }\n return 'earlier'\n}\n\n/**\n * The session list beside a full-page chat (study's `ChatSessionList` +\n * health's history overlay, generalized): fuzzy search, date grouping,\n * active marking, per-session actions (rename/delete/export) via the\n * menu when handlers are provided. All actions stay app-side.\n */\nexport const ChatSessionList = React.forwardRef<HTMLDivElement, ChatSessionListProps>(\n function ChatSessionList(\n {\n sessions,\n activeId,\n onSelect,\n onNew,\n onRename,\n onRenameCommit,\n onDelete,\n onExport,\n searchable = true,\n groupByDate = true,\n labels,\n icons,\n className,\n },\n ref,\n ) {\n const [query, setQuery] = React.useState('')\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draftTitle, setDraftTitle] = React.useState('')\n const NewIcon = icons?.newChat ?? MessageSquarePlus\n const RenameIcon = icons?.rename ?? Pencil\n const DeleteIcon = icons?.delete ?? Trash2\n const ExportIcon = icons?.export ?? Download\n\n const filtered = React.useMemo(() => {\n const trimmed = query.trim()\n if (trimmed === '') {\n return sessions\n }\n return sessions\n .map((session) => ({\n session,\n score: searchScore(trimmed, session.title),\n }))\n .filter((entry) => entry.score !== null)\n .sort((a, b) => (b.score ?? 0) - (a.score ?? 0))\n .map((entry) => entry.session)\n }, [sessions, query])\n\n const groups = React.useMemo(() => {\n if (!groupByDate) {\n return [{ key: 'all', label: null as string | null, sessions: filtered }]\n }\n const ordered = [...filtered].sort((a, b) => {\n const dateA = toDate(a.updatedAt)?.getTime() ?? 0\n const dateB = toDate(b.updatedAt)?.getTime() ?? 0\n return dateB - dateA\n })\n const byBucket = new Map<string, ChatSessionView[]>()\n for (const session of ordered) {\n const bucket = bucketOf(toDate(session.updatedAt))\n const list = byBucket.get(bucket) ?? []\n list.push(session)\n byBucket.set(bucket, list)\n }\n const order = ['today', 'yesterday', 'earlier', 'none']\n return order\n .filter((bucket) => byBucket.has(bucket))\n .map((bucket) => ({\n key: bucket,\n label:\n bucket === 'today'\n ? (labels?.today ?? 'Today')\n : bucket === 'yesterday'\n ? (labels?.yesterday ?? 'Yesterday')\n : bucket === 'earlier'\n ? (labels?.earlier ?? 'Earlier')\n : null,\n sessions: byBucket.get(bucket) ?? [],\n }))\n }, [filtered, groupByDate, labels])\n\n return (\n <div ref={ref} data-as=\"chat-session-list\" className={cn('flex h-full min-h-0 w-full flex-col gap-2', className)}>\n <div className=\"flex items-center gap-2\">\n {searchable ? (\n <div className=\"relative min-w-0 flex-1\">\n <Search className=\"pointer-events-none absolute start-2 top-1/2 size-3.5 -translate-y-1/2 text-[var(--as-muted-fg)]\" aria-hidden />\n <input\n type=\"search\"\n role=\"searchbox\"\n value={query}\n aria-label={labels?.search ?? 'Search conversations'}\n placeholder={labels?.searchPlaceholder ?? 'Search…'}\n onChange={(event) => setQuery(event.target.value)}\n className=\"w-full rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] py-1.5 pe-2 ps-8 text-xs text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n />\n </div>\n ) : null}\n {onNew ? (\n <button\n type=\"button\"\n onClick={onNew}\n aria-label={labels?.newChat ?? 'New conversation'}\n title={labels?.newChat ?? 'New conversation'}\n className=\"flex size-7 shrink-0 items-center justify-center rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-[var(--as-fg)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <NewIcon className=\"size-4\" aria-hidden />\n </button>\n ) : null}\n </div>\n {sessions.length === 0 ? (\n <p className=\"px-2 py-3 text-xs text-[var(--as-muted-fg)]\">{labels?.empty ?? 'No conversations yet'}</p>\n ) : filtered.length === 0 ? (\n <p className=\"px-2 py-3 text-xs text-[var(--as-muted-fg)]\">{labels?.noResults ?? 'No matches'}</p>\n ) : (\n <div role=\"list\" aria-label={labels?.search ?? 'Conversations'} data-as=\"chat-session-rows\" className=\"min-h-0 flex-1 overflow-y-auto\">\n {groups.map((group) => (\n <div key={group.key} className=\"mb-2\">\n {group.label ? (\n <p className=\"px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]\">\n {group.label}\n </p>\n ) : null}\n {group.sessions.map((session) => {\n const isActive = session.id === activeId\n const date = toDate(session.updatedAt)\n const isEditing = editingId === session.id\n const commitRename = () => {\n if (editingId !== null && draftTitle.trim() !== '') {\n onRenameCommit?.(editingId, draftTitle.trim())\n }\n setEditingId(null)\n }\n const actions =\n onRename || onRenameCommit || onDelete || onExport\n ? [\n onRename || onRenameCommit\n ? {\n key: 'rename',\n label: labels?.rename ?? 'Rename',\n icon: <RenameIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => {\n if (onRenameCommit !== undefined) {\n setEditingId(session.id)\n setDraftTitle(session.title)\n } else {\n onRename?.(session.id)\n }\n },\n }\n : null,\n onExport\n ? {\n key: 'export',\n label: labels?.export ?? 'Export',\n icon: <ExportIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => onExport(session.id),\n }\n : null,\n onDelete\n ? {\n key: 'delete',\n label: labels?.delete ?? 'Delete',\n icon: <DeleteIcon className=\"size-3.5\" aria-hidden />,\n onSelect: () => onDelete(session.id),\n danger: true,\n }\n : null,\n ].filter((item): item is NonNullable<typeof item> => item !== null)\n : []\n return (\n <div\n key={session.id}\n role=\"listitem\"\n aria-current={isActive ? 'true' : undefined}\n data-active={isActive || undefined}\n className={cn(\n 'group/session flex cursor-pointer items-center gap-2 rounded-[var(--as-radius)] px-2 py-1.5 text-xs transition-colors hover:bg-[var(--as-surface-raised)]',\n isActive && 'bg-[var(--as-surface-raised)] font-medium text-[var(--as-fg)]',\n )}\n onClick={() => onSelect(session.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n onSelect(session.id)\n }\n }}\n tabIndex={0}\n >\n {isEditing ? (\n <input\n autoFocus\n value={draftTitle}\n aria-label={labels?.renameInput ?? 'Conversation title'}\n onChange={(event) => setDraftTitle(event.target.value)}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === 'Enter') {\n event.preventDefault()\n commitRename()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n setEditingId(null)\n }\n }}\n onBlur={commitRename}\n className=\"min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-primary)] bg-[var(--as-surface)] px-1.5 py-0.5 text-xs text-[var(--as-fg)] focus-visible:outline-none\"\n />\n ) : (\n <span className=\"line-clamp-2 min-w-0 flex-1 text-[var(--as-fg)]\">{session.title}</span>\n )}\n {date ? (\n <span className=\"shrink-0 text-[10px] text-[var(--as-muted-fg)]\">\n {formatDistanceToNow(date, { addSuffix: true })}\n </span>\n ) : null}\n {actions.length > 0 ? (\n <span\n role=\"group\"\n aria-label={labels?.rename ?? 'Conversation actions'}\n className=\"flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity focus-within:opacity-100 group-hover/session:opacity-100\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n >\n {actions.map((action) => (\n <button\n key={action.key}\n type=\"button\"\n aria-label={action.label}\n title={action.label}\n onClick={action.onSelect}\n className={cn(\n 'rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface)] hover:text-[var(--as-fg)] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]',\n action.danger && 'hover:text-[var(--as-danger)]',\n )}\n >\n {action.icon}\n </button>\n ))}\n </span>\n ) : null}\n </div>\n )\n })}\n </div>\n ))\n }\n </div>\n )}\n </div>\n )\n },\n)\n"]}
@@ -0,0 +1,264 @@
1
+ import { Button } from './chunk-4WBBF5LH.js';
2
+ import { cn } from './chunk-RVVXVTH3.js';
3
+ import * as React3 from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+ import { ChevronLeft, ChevronRight, Check, Copy, Pencil, RotateCcw, TriangleAlert } from 'lucide-react';
6
+
7
+ var ChatMessageEditor = React3.forwardRef(
8
+ function ChatMessageEditor2({ value, onValueChange, onSubmit, onCancel, submitDisabled = false, rows = 3, labels, className }, ref) {
9
+ const onKeyDown = (event) => {
10
+ if (event.key === "Escape") {
11
+ event.preventDefault();
12
+ onCancel();
13
+ return;
14
+ }
15
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
16
+ event.preventDefault();
17
+ if (!submitDisabled) {
18
+ onSubmit();
19
+ }
20
+ }
21
+ };
22
+ return /* @__PURE__ */ jsxs("div", { ref, "data-as": "chat-message-editor", className: cn("as-anim-slide-up flex w-full flex-col gap-2", className), children: [
23
+ /* @__PURE__ */ jsx(
24
+ "textarea",
25
+ {
26
+ autoFocus: true,
27
+ value,
28
+ rows,
29
+ "aria-label": labels?.ariaLabel ?? "Edit message",
30
+ onChange: (event) => onValueChange(event.target.value),
31
+ onKeyDown,
32
+ className: "w-full resize-y rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]"
33
+ }
34
+ ),
35
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
36
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onCancel, children: labels?.cancel ?? "Cancel" }),
37
+ /* @__PURE__ */ jsx(Button, { size: "sm", disabled: submitDisabled, onClick: onSubmit, children: labels?.save ?? "Save & resend" })
38
+ ] })
39
+ ] });
40
+ }
41
+ );
42
+ var MessageVariantSwitcher = React3.forwardRef(
43
+ function MessageVariantSwitcher2({ variants, onSelect, labels, className }, ref) {
44
+ const { index, count, siblingIds } = variants;
45
+ const previousId = index > 1 ? siblingIds[index - 2] : void 0;
46
+ const nextId = index < count ? siblingIds[index] : void 0;
47
+ const select = (event, messageId) => {
48
+ event.stopPropagation();
49
+ if (messageId !== void 0) {
50
+ onSelect(messageId);
51
+ }
52
+ };
53
+ return /* @__PURE__ */ jsxs(
54
+ "div",
55
+ {
56
+ ref,
57
+ role: "group",
58
+ "aria-label": labels?.group ?? "Message variants",
59
+ "data-as": "message-variant-switcher",
60
+ className: cn("flex items-center gap-0.5 text-xs text-[var(--as-muted-fg)]", className),
61
+ children: [
62
+ /* @__PURE__ */ jsx(
63
+ "button",
64
+ {
65
+ type: "button",
66
+ disabled: previousId === void 0,
67
+ "aria-label": labels?.previous ?? "Previous variant",
68
+ title: labels?.previous ?? "Previous variant",
69
+ onClick: (event) => select(event, previousId),
70
+ className: "rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
71
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5", "aria-hidden": true })
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsxs("span", { className: "tabular-nums", title: labels?.variantOf?.(index, count) ?? `Variant ${index} of ${count}`, children: [
75
+ index,
76
+ "/",
77
+ count
78
+ ] }),
79
+ /* @__PURE__ */ jsx(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ disabled: nextId === void 0,
84
+ "aria-label": labels?.next ?? "Next variant",
85
+ title: labels?.next ?? "Next variant",
86
+ onClick: (event) => select(event, nextId),
87
+ className: "rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
88
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5", "aria-hidden": true })
89
+ }
90
+ )
91
+ ]
92
+ }
93
+ );
94
+ }
95
+ );
96
+ var actionButtonClass = "rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]";
97
+ var COPIED_FEEDBACK_MS = 1500;
98
+ var ChatMessage = React3.forwardRef(
99
+ function ChatMessage2({
100
+ role,
101
+ content,
102
+ reasoning,
103
+ children,
104
+ status = "done",
105
+ error,
106
+ actions,
107
+ editing = false,
108
+ variants,
109
+ onSelectVariant,
110
+ meta,
111
+ chips,
112
+ attachments,
113
+ avatar,
114
+ design = "bubbles",
115
+ labels,
116
+ compact = false,
117
+ className
118
+ }, ref) {
119
+ const isUser = role === "user";
120
+ const isSystem = role === "system";
121
+ const showActions = actions !== void 0 && (actions.onCopy !== void 0 || actions.onEdit !== void 0 || actions.onRegenerate !== void 0 || (actions.extras?.length ?? 0) > 0);
122
+ const editorLabels = { save: labels?.save, cancel: labels?.cancel, ariaLabel: labels?.ariaLabel };
123
+ const switcherLabels = { group: labels?.group, variantOf: labels?.variantOf, previous: labels?.previous, next: labels?.next };
124
+ const [copiedAt, setCopiedAt] = React3.useState(null);
125
+ const copied = actions?.copied ?? copiedAt !== null;
126
+ React3.useEffect(() => {
127
+ if (copiedAt === null) {
128
+ return;
129
+ }
130
+ const id = window.setTimeout(() => setCopiedAt(null), COPIED_FEEDBACK_MS);
131
+ return () => window.clearTimeout(id);
132
+ }, [copiedAt]);
133
+ const onCopyClick = () => {
134
+ actions?.onCopy?.();
135
+ if (actions?.copied === void 0) {
136
+ setCopiedAt(Date.now());
137
+ }
138
+ };
139
+ if (isSystem) {
140
+ return /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ ref,
144
+ "data-as": "chat-message",
145
+ "data-role": role,
146
+ className: cn("flex justify-center", className),
147
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-full border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-1 text-xs text-[var(--as-muted-fg)]", children: content })
148
+ }
149
+ );
150
+ }
151
+ return /* @__PURE__ */ jsxs(
152
+ "div",
153
+ {
154
+ ref,
155
+ "data-as": "chat-message",
156
+ "data-role": role,
157
+ "data-status": status,
158
+ "data-design": design,
159
+ className: cn("as-anim-message-in group/message flex w-full flex-col", isUser ? "items-end" : "items-start", className),
160
+ children: [
161
+ attachments ? /* @__PURE__ */ jsx("div", { className: "mb-1.5 flex w-full flex-wrap justify-end gap-1.5", children: attachments }) : null,
162
+ (() => {
163
+ const bubble = /* @__PURE__ */ jsxs(
164
+ "div",
165
+ {
166
+ className: cn(
167
+ "relative flex max-w-[92%] flex-col gap-1.5 rounded-[var(--as-radius)] px-3 py-2 text-sm",
168
+ compact && "px-2.5 py-1.5",
169
+ isUser ? "rounded-br-[var(--as-radius-sm)] bg-[var(--as-primary)] font-medium text-[var(--as-primary-fg)]" : design === "flat" ? "max-w-full rounded-bl-[var(--as-radius-sm)] bg-transparent px-0 py-0" : "rounded-bl-[var(--as-radius-sm)] bg-[var(--as-muted)] text-[var(--as-fg)]"
170
+ ),
171
+ children: [
172
+ reasoning ? /* @__PURE__ */ jsx("div", { className: "not-prose", children: reasoning }) : null,
173
+ editing ? /* @__PURE__ */ jsx(
174
+ ChatMessageEditor,
175
+ {
176
+ value: editing.value,
177
+ onValueChange: editing.onValueChange,
178
+ onSubmit: editing.onSubmit,
179
+ onCancel: editing.onCancel,
180
+ submitDisabled: editing.submitDisabled,
181
+ labels: editorLabels
182
+ }
183
+ ) : /* @__PURE__ */ jsx(
184
+ "div",
185
+ {
186
+ className: cn("min-w-0", variants && variants.count > 1 && "as-anim-fade-swap"),
187
+ children: content
188
+ },
189
+ variants && variants.count > 1 ? `variant-${variants.index}` : void 0
190
+ ),
191
+ status === "interrupted" ? /* @__PURE__ */ jsx("span", { className: "as-anim-slide-up as-anim-pulse-once [--as-pulse-color:var(--as-warning)] self-start rounded-full bg-[var(--as-warning)]/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--as-warning)]", children: labels?.interrupted ?? "Stopped" }) : null,
192
+ status === "error" && error ? /* @__PURE__ */ jsxs(
193
+ "div",
194
+ {
195
+ role: "alert",
196
+ className: "as-anim-slide-up flex flex-col gap-1 rounded-[var(--as-radius)] border border-[var(--as-danger)]/40 bg-[var(--as-danger)]/10 px-2.5 py-1.5 text-xs text-[var(--as-danger)]",
197
+ children: [
198
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold uppercase tracking-wide", children: error.code }),
199
+ /* @__PURE__ */ jsx("span", { className: "whitespace-pre-wrap break-words", children: error.message }),
200
+ error.retryable && actions?.onRetry ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
201
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "self-start", onClick: actions.onRetry, children: labels?.retry ?? "Retry" }),
202
+ actions?.errorExtra
203
+ ] }) : actions?.errorExtra ? /* @__PURE__ */ jsx("div", { className: "self-start", children: actions.errorExtra }) : null
204
+ ]
205
+ }
206
+ ) : null,
207
+ chips ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: chips }) : null,
208
+ children ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children }) : null,
209
+ showActions && !editing ? /* @__PURE__ */ jsxs(
210
+ "div",
211
+ {
212
+ "data-as": "chat-message-actions",
213
+ className: cn(
214
+ "absolute top-1 flex items-center gap-0.5 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-0.5 shadow-[var(--as-shadow-1)]",
215
+ isUser ? "-left-9" : "-right-9"
216
+ ),
217
+ children: [
218
+ actions?.onCopy ? /* @__PURE__ */ jsx(
219
+ "button",
220
+ {
221
+ type: "button",
222
+ className: actionButtonClass,
223
+ "aria-label": copied ? labels?.copied ?? "Copied" : labels?.copy ?? "Copy",
224
+ title: copied ? labels?.copied ?? "Copied" : labels?.copy ?? "Copy",
225
+ onClick: onCopyClick,
226
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "as-anim-fade-swap size-3.5 text-[var(--as-success)]", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5", "aria-hidden": true })
227
+ }
228
+ ) : null,
229
+ actions?.onEdit ? /* @__PURE__ */ jsx("button", { type: "button", className: actionButtonClass, "aria-label": labels?.edit ?? "Edit", title: labels?.edit ?? "Edit", onClick: actions.onEdit, children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5", "aria-hidden": true }) }) : null,
230
+ actions?.onRegenerate ? /* @__PURE__ */ jsx("button", { type: "button", className: actionButtonClass, "aria-label": labels?.regenerate ?? "Regenerate", title: labels?.regenerate ?? "Regenerate", onClick: actions.onRegenerate, children: /* @__PURE__ */ jsx(RotateCcw, { className: "size-3.5", "aria-hidden": true }) }) : null,
231
+ actions?.extras?.map((extra) => /* @__PURE__ */ jsx(
232
+ "button",
233
+ {
234
+ type: "button",
235
+ className: cn(actionButtonClass, extra.danger && "hover:text-[var(--as-danger)]"),
236
+ "aria-label": extra.label,
237
+ title: extra.label,
238
+ onClick: extra.onClick,
239
+ children: extra.icon ? /* @__PURE__ */ jsx(extra.icon, { className: "size-3.5", "aria-hidden": true }) : /* @__PURE__ */ jsx(TriangleAlert, { className: "size-3.5", "aria-hidden": true })
240
+ },
241
+ extra.key
242
+ ))
243
+ ]
244
+ }
245
+ ) : null
246
+ ]
247
+ }
248
+ );
249
+ return avatar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: cn("flex w-full items-end gap-2", isUser ? "flex-row-reverse" : "flex-row"), children: [
250
+ /* @__PURE__ */ jsx("div", { "data-as": "chat-message-avatar", className: "shrink-0 pb-1", children: avatar }),
251
+ bubble
252
+ ] }) : bubble;
253
+ })(),
254
+ meta ? /* @__PURE__ */ jsx("div", { className: "mt-0.5 flex flex-wrap items-center gap-1.5 px-1 text-[10px] text-[var(--as-muted-fg)]", children: meta }) : null,
255
+ variants && variants.count > 1 && onSelectVariant ? /* @__PURE__ */ jsx(MessageVariantSwitcher, { variants, onSelect: onSelectVariant, labels: switcherLabels, className: "mt-0.5 px-1" }) : null
256
+ ]
257
+ }
258
+ );
259
+ }
260
+ );
261
+
262
+ export { ChatMessage, ChatMessageEditor, MessageVariantSwitcher };
263
+ //# sourceMappingURL=chunk-56QSIK4Y.js.map
264
+ //# sourceMappingURL=chunk-56QSIK4Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-message/ChatMessageEditor.tsx","../src/components/chat-message/MessageVariantSwitcher.tsx","../src/components/chat-message/ChatMessage.tsx"],"names":["React","ChatMessageEditor","React2","MessageVariantSwitcher","jsxs","jsx","ChatMessage"],"mappings":"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","file":"chunk-56QSIK4Y.js","sourcesContent":["import * as React from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\n\nexport interface ChatMessageEditorLabels {\n save: string\n cancel: string\n ariaLabel: string\n}\n\nexport interface ChatMessageEditorProps {\n value: string\n onValueChange: (value: string) => void\n /** Save & resend — branches the tree (family `edit` endpoint) and reruns the turn. */\n onSubmit: () => void\n onCancel: () => void\n submitDisabled?: boolean\n rows?: number\n labels?: Partial<ChatMessageEditorLabels>\n className?: string\n}\n\n/**\n * Inline user-prompt editor for the edit-and-resend flow (study pattern):\n * controlled textarea, Cmd/Ctrl+Enter saves, Escape cancels.\n */\nexport const ChatMessageEditor = React.forwardRef<HTMLDivElement, ChatMessageEditorProps>(\n function ChatMessageEditor(\n { value, onValueChange, onSubmit, onCancel, submitDisabled = false, rows = 3, labels, className },\n ref,\n ) {\n const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!submitDisabled) {\n onSubmit()\n }\n }\n }\n\n return (\n <div ref={ref} data-as=\"chat-message-editor\" className={cn('as-anim-slide-up flex w-full flex-col gap-2', className)}>\n <textarea\n autoFocus\n value={value}\n rows={rows}\n aria-label={labels?.ariaLabel ?? 'Edit message'}\n onChange={(event) => onValueChange(event.target.value)}\n onKeyDown={onKeyDown}\n className=\"w-full resize-y rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n />\n <div className=\"flex items-center justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {labels?.cancel ?? 'Cancel'}\n </Button>\n <Button size=\"sm\" disabled={submitDisabled} onClick={onSubmit}>\n {labels?.save ?? 'Save & resend'}\n </Button>\n </div>\n </div>\n )\n },\n)\n","import * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nimport type { ChatMessageVariants } from '../chat-core/types'\nimport { cn } from '../../lib/utils'\n\nexport interface MessageVariantSwitcherLabels {\n group: string\n variantOf: (index: number, count: number) => string\n previous: string\n next: string\n}\n\nexport interface MessageVariantSwitcherProps {\n variants: ChatMessageVariants\n /** Flip the parent's active-child pointer to the sibling id (family `select` endpoint). */\n onSelect: (messageId: string) => void\n labels?: Partial<MessageVariantSwitcherLabels>\n className?: string\n}\n\n/**\n * The `‹ n/N ›` sibling-variant switcher under a branched message\n * (study's inline switcher, family branching contract).\n */\nexport const MessageVariantSwitcher = React.forwardRef<HTMLDivElement, MessageVariantSwitcherProps>(\n function MessageVariantSwitcher({ variants, onSelect, labels, className }, ref) {\n const { index, count, siblingIds } = variants\n const previousId = index > 1 ? siblingIds[index - 2] : undefined\n const nextId = index < count ? siblingIds[index] : undefined\n\n const select = (event: React.MouseEvent<HTMLButtonElement>, messageId: string | undefined) => {\n event.stopPropagation()\n if (messageId !== undefined) {\n onSelect(messageId)\n }\n }\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={labels?.group ?? 'Message variants'}\n data-as=\"message-variant-switcher\"\n className={cn('flex items-center gap-0.5 text-xs text-[var(--as-muted-fg)]', className)}\n >\n <button\n type=\"button\"\n disabled={previousId === undefined}\n aria-label={labels?.previous ?? 'Previous variant'}\n title={labels?.previous ?? 'Previous variant'}\n onClick={(event) => select(event, previousId)}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronLeft className=\"size-3.5\" aria-hidden />\n </button>\n <span className=\"tabular-nums\" title={labels?.variantOf?.(index, count) ?? `Variant ${index} of ${count}`}>\n {index}/{count}\n </span>\n <button\n type=\"button\"\n disabled={nextId === undefined}\n aria-label={labels?.next ?? 'Next variant'}\n title={labels?.next ?? 'Next variant'}\n onClick={(event) => select(event, nextId)}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronRight className=\"size-3.5\" aria-hidden />\n </button>\n </div>\n )\n },\n)\n","import * as React from 'react'\nimport { Check, Copy, Pencil, RotateCcw, TriangleAlert } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\nimport type { ChatError, ChatMessageVariants, ChatRole } from '../chat-core/types'\nimport { ChatMessageEditor, type ChatMessageEditorLabels } from './ChatMessageEditor'\nimport { MessageVariantSwitcher, type MessageVariantSwitcherLabels } from './MessageVariantSwitcher'\n\nexport interface ChatMessageAction {\n key: string\n label: string\n icon?: LucideIcon\n onClick: () => void\n danger?: boolean\n}\n\nexport interface ChatMessageActions {\n onCopy?: () => void\n /**\n * Controlled copied feedback for the copy action (check morph).\n * Uncontrolled by default: the library shows the check for 1.5 s\n * after each copy click.\n */\n copied?: boolean\n onEdit?: () => void\n onRegenerate?: () => void\n onRetry?: () => void\n /** Extra icon actions (app-specific). */\n extras?: ChatMessageAction[]\n /**\n * Extra node rendered inside the error card next to the Retry button —\n * e.g. an \"Open AI settings\" link when the error is a not-configured\n * failure. Rendered only while the error card is visible.\n */\n errorExtra?: React.ReactNode\n}\n\nexport interface ChatMessageLabels {\n copy: string\n copied: string\n edit: string\n regenerate: string\n retry: string\n interrupted: string\n error: string\n}\n\nexport interface ChatMessageProps {\n role: ChatRole\n /** Usually a `MarkdownSurface`; any node graph renders here. */\n content: React.ReactNode\n /** Thinking block slot (usually `ChatReasoning`). */\n reasoning?: React.ReactNode\n /** Tool cards / HITL cards below the content. */\n children?: React.ReactNode\n status?: 'streaming' | 'done' | 'error' | 'interrupted'\n error?: ChatError\n actions?: ChatMessageActions\n /** Edit-and-resend mode for user messages (renders `ChatMessageEditor`). */\n editing?: false | { value: string; onValueChange: (value: string) => void; onSubmit: () => void; onCancel: () => void; submitDisabled?: boolean }\n variants?: ChatMessageVariants\n onSelectVariant?: (messageId: string) => void\n /** Badge row (model, latency, tokens — app-defined). */\n meta?: React.ReactNode\n /** Deep-link chip row (career references, health citations). */\n chips?: React.ReactNode\n /** Attachment rail above the content. */\n attachments?: React.ReactNode\n /** Leading identity node beside the bubble (user: trailing). Render group-start avatars only when grouping transcripts. */\n avatar?: React.ReactNode\n /**\n * Visual design: `bubbles` (default) — paired role bubbles; `flat` —\n * assistant content renders as prose on the panel surface (user keeps\n * its bubble). Reasoning/tool cards are unaffected.\n */\n design?: 'bubbles' | 'flat'\n labels?: Partial<ChatMessageLabels & ChatMessageEditorLabels & MessageVariantSwitcherLabels>\n compact?: boolean\n className?: string\n}\n\nconst actionButtonClass =\n 'rounded-[var(--as-radius-sm)] p-1 text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-surface-raised)] hover:text-[var(--as-fg)] focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]'\n\nconst COPIED_FEEDBACK_MS = 1500\n\n/**\n * The family chat bubble: role-aligned, hover action row (copy / edit /\n * regenerate + app extras), variant switcher under branched messages,\n * reasoning and below-content slots, inline edit-and-resend, error +\n * interrupted states. Presentational — state lives app-side.\n */\nexport const ChatMessage = React.forwardRef<HTMLDivElement, ChatMessageProps>(\n function ChatMessage(\n {\n role,\n content,\n reasoning,\n children,\n status = 'done',\n error,\n actions,\n editing = false,\n variants,\n onSelectVariant,\n meta,\n chips,\n attachments,\n avatar,\n design = 'bubbles',\n labels,\n compact = false,\n className,\n },\n ref,\n ) {\n const isUser = role === 'user'\n const isSystem = role === 'system'\n const showActions =\n actions !== undefined && (actions.onCopy !== undefined || actions.onEdit !== undefined || actions.onRegenerate !== undefined || (actions.extras?.length ?? 0) > 0)\n const editorLabels = { save: labels?.save, cancel: labels?.cancel, ariaLabel: labels?.ariaLabel }\n const switcherLabels = { group: labels?.group, variantOf: labels?.variantOf, previous: labels?.previous, next: labels?.next }\n const [copiedAt, setCopiedAt] = React.useState<number | null>(null)\n const copied = actions?.copied ?? copiedAt !== null\n\n React.useEffect(() => {\n if (copiedAt === null) {\n return\n }\n const id = window.setTimeout(() => setCopiedAt(null), COPIED_FEEDBACK_MS)\n return () => window.clearTimeout(id)\n }, [copiedAt])\n\n const onCopyClick = () => {\n actions?.onCopy?.()\n if (actions?.copied === undefined) {\n setCopiedAt(Date.now())\n }\n }\n\n if (isSystem) {\n return (\n <div\n ref={ref}\n data-as=\"chat-message\"\n data-role={role}\n className={cn('flex justify-center', className)}\n >\n <div className=\"rounded-full border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-1 text-xs text-[var(--as-muted-fg)]\">\n {content}\n </div>\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n data-as=\"chat-message\"\n data-role={role}\n data-status={status}\n data-design={design}\n className={cn('as-anim-message-in group/message flex w-full flex-col', isUser ? 'items-end' : 'items-start', className)}\n >\n {attachments ? <div className=\"mb-1.5 flex w-full flex-wrap justify-end gap-1.5\">{attachments}</div> : null}\n {(() => {\n const bubble = (\n <div\n className={cn(\n 'relative flex max-w-[92%] flex-col gap-1.5 rounded-[var(--as-radius)] px-3 py-2 text-sm',\n compact && 'px-2.5 py-1.5',\n isUser\n ? 'rounded-br-[var(--as-radius-sm)] bg-[var(--as-primary)] font-medium text-[var(--as-primary-fg)]'\n : design === 'flat'\n ? 'max-w-full rounded-bl-[var(--as-radius-sm)] bg-transparent px-0 py-0'\n : 'rounded-bl-[var(--as-radius-sm)] bg-[var(--as-muted)] text-[var(--as-fg)]',\n )}\n >\n {reasoning ? <div className=\"not-prose\">{reasoning}</div> : null}\n {editing ? (\n <ChatMessageEditor\n value={editing.value}\n onValueChange={editing.onValueChange}\n onSubmit={editing.onSubmit}\n onCancel={editing.onCancel}\n submitDisabled={editing.submitDisabled}\n labels={editorLabels}\n />\n ) : (\n <div\n key={variants && variants.count > 1 ? `variant-${variants.index}` : undefined}\n className={cn('min-w-0', variants && variants.count > 1 && 'as-anim-fade-swap')}\n >\n {content}\n </div>\n )}\n {status === 'interrupted' ? (\n <span className=\"as-anim-slide-up as-anim-pulse-once [--as-pulse-color:var(--as-warning)] self-start rounded-full bg-[var(--as-warning)]/15 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--as-warning)]\">\n {labels?.interrupted ?? 'Stopped'}\n </span>\n ) : null}\n {status === 'error' && error ? (\n <div\n role=\"alert\"\n className=\"as-anim-slide-up flex flex-col gap-1 rounded-[var(--as-radius)] border border-[var(--as-danger)]/40 bg-[var(--as-danger)]/10 px-2.5 py-1.5 text-xs text-[var(--as-danger)]\"\n >\n <span className=\"font-mono font-semibold uppercase tracking-wide\">{error.code}</span>\n <span className=\"whitespace-pre-wrap break-words\">{error.message}</span>\n {error.retryable && actions?.onRetry ? (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Button variant=\"outline\" size=\"sm\" className=\"self-start\" onClick={actions.onRetry}>\n {labels?.retry ?? 'Retry'}\n </Button>\n {actions?.errorExtra}\n </div>\n ) : actions?.errorExtra ? (\n <div className=\"self-start\">{actions.errorExtra}</div>\n ) : null}\n </div>\n ) : null}\n {chips ? <div className=\"flex flex-wrap gap-1.5\">{chips}</div> : null}\n {children ? <div className=\"flex flex-col gap-1.5\">{children}</div> : null}\n {showActions && !editing ? (\n <div\n data-as=\"chat-message-actions\"\n className={cn(\n 'absolute top-1 flex items-center gap-0.5 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-0.5 shadow-[var(--as-shadow-1)]',\n isUser ? '-left-9' : '-right-9',\n )}\n >\n {actions?.onCopy ? (\n <button\n type=\"button\"\n className={actionButtonClass}\n aria-label={copied ? (labels?.copied ?? 'Copied') : (labels?.copy ?? 'Copy')}\n title={copied ? (labels?.copied ?? 'Copied') : (labels?.copy ?? 'Copy')}\n onClick={onCopyClick}\n >\n {copied ? (\n <Check className=\"as-anim-fade-swap size-3.5 text-[var(--as-success)]\" aria-hidden />\n ) : (\n <Copy className=\"size-3.5\" aria-hidden />\n )}\n </button>\n ) : null}\n {actions?.onEdit ? (\n <button type=\"button\" className={actionButtonClass} aria-label={labels?.edit ?? 'Edit'} title={labels?.edit ?? 'Edit'} onClick={actions.onEdit}>\n <Pencil className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n {actions?.onRegenerate ? (\n <button type=\"button\" className={actionButtonClass} aria-label={labels?.regenerate ?? 'Regenerate'} title={labels?.regenerate ?? 'Regenerate'} onClick={actions.onRegenerate}>\n <RotateCcw className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n {actions?.extras?.map((extra) => (\n <button\n key={extra.key}\n type=\"button\"\n className={cn(actionButtonClass, extra.danger && 'hover:text-[var(--as-danger)]')}\n aria-label={extra.label}\n title={extra.label}\n onClick={extra.onClick}\n >\n {extra.icon ? <extra.icon className=\"size-3.5\" aria-hidden /> : <TriangleAlert className=\"size-3.5\" aria-hidden />}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n )\n return avatar !== undefined ? (\n <div className={cn('flex w-full items-end gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>\n <div data-as=\"chat-message-avatar\" className=\"shrink-0 pb-1\">\n {avatar}\n </div>\n {bubble}\n </div>\n ) : (\n bubble\n )\n })()}\n {meta ? <div className=\"mt-0.5 flex flex-wrap items-center gap-1.5 px-1 text-[10px] text-[var(--as-muted-fg)]\">{meta}</div> : null}\n {variants && variants.count > 1 && onSelectVariant ? (\n <MessageVariantSwitcher variants={variants} onSelect={onSelectVariant} labels={switcherLabels} className=\"mt-0.5 px-1\" />\n ) : null}\n </div>\n )\n },\n)\n"]}