@agent-native/toolkit 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +17 -1
  2. package/dist/chat-history/ChatHistoryList.d.ts +103 -0
  3. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
  4. package/dist/chat-history/ChatHistoryList.js +109 -0
  5. package/dist/chat-history/ChatHistoryList.js.map +1 -0
  6. package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
  7. package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
  8. package/dist/chat-history/ChatHistoryList.spec.js +283 -0
  9. package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
  10. package/dist/chat-history/index.d.ts +2 -0
  11. package/dist/chat-history/index.d.ts.map +1 -0
  12. package/dist/chat-history/index.js +2 -0
  13. package/dist/chat-history/index.js.map +1 -0
  14. package/dist/chat-history.css +314 -0
  15. package/package.json +17 -1
  16. package/src/app-shell/header-actions.tsx +84 -0
  17. package/src/app-shell/index.ts +1 -0
  18. package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
  19. package/src/chat-history/ChatHistoryList.tsx +469 -0
  20. package/src/chat-history/index.ts +7 -0
  21. package/src/chat-history.css +314 -0
  22. package/src/collab-ui/AgentPresenceChip.tsx +69 -0
  23. package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  24. package/src/collab-ui/PresenceBar.tsx +346 -0
  25. package/src/collab-ui/RecentEditHighlights.tsx +225 -0
  26. package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  27. package/src/collab-ui/agent-identity.ts +16 -0
  28. package/src/collab-ui/index.ts +39 -0
  29. package/src/collab-ui/lead-client.ts +32 -0
  30. package/src/collab-ui/types.spec.ts +18 -0
  31. package/src/collab-ui/types.ts +96 -0
  32. package/src/composer/AgentComposerFrame.tsx +59 -0
  33. package/src/composer/ComposerPlusMenu.tsx +983 -0
  34. package/src/composer/MentionPopover.tsx +412 -0
  35. package/src/composer/PastedTextChip.tsx +150 -0
  36. package/src/composer/PromptComposer.spec.ts +73 -0
  37. package/src/composer/PromptComposer.tsx +718 -0
  38. package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
  39. package/src/composer/RealtimeVoiceMode.tsx +856 -0
  40. package/src/composer/TiptapComposer.spec.ts +390 -0
  41. package/src/composer/TiptapComposer.tsx +2936 -0
  42. package/src/composer/VoiceButton.spec.ts +33 -0
  43. package/src/composer/VoiceButton.tsx +255 -0
  44. package/src/composer/asset-picker-url.spec.ts +56 -0
  45. package/src/composer/asset-picker-url.ts +80 -0
  46. package/src/composer/attachment-accept.spec.ts +22 -0
  47. package/src/composer/attachment-accept.ts +71 -0
  48. package/src/composer/draft-key.spec.ts +19 -0
  49. package/src/composer/draft-key.ts +7 -0
  50. package/src/composer/extensions/FileReference.tsx +55 -0
  51. package/src/composer/extensions/MentionReference.tsx +93 -0
  52. package/src/composer/extensions/SkillReference.tsx +56 -0
  53. package/src/composer/index.ts +79 -0
  54. package/src/composer/pasted-text.spec.ts +162 -0
  55. package/src/composer/pasted-text.ts +160 -0
  56. package/src/composer/prompt-attachments.spec.ts +80 -0
  57. package/src/composer/prompt-attachments.ts +93 -0
  58. package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
  59. package/src/composer/realtime-voice-audio-level.ts +64 -0
  60. package/src/composer/realtime-voice-transcript.spec.ts +95 -0
  61. package/src/composer/realtime-voice-transcript.ts +134 -0
  62. package/src/composer/runtime-adapters.tsx +343 -0
  63. package/src/composer/types.ts +66 -0
  64. package/src/composer/use-file-search.ts +64 -0
  65. package/src/composer/use-mention-search.ts +90 -0
  66. package/src/composer/use-skills.ts +45 -0
  67. package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
  68. package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  69. package/src/composer/useVoiceDictation.spec.ts +45 -0
  70. package/src/composer/useVoiceDictation.ts +1191 -0
  71. package/src/context-ui/ContextMeter.tsx +101 -0
  72. package/src/context-ui/ContextSegmentRow.tsx +184 -0
  73. package/src/context-ui/ContextTreemap.tsx +125 -0
  74. package/src/context-ui/ContextXRayPanel.tsx +377 -0
  75. package/src/context-ui/format.ts +42 -0
  76. package/src/context-ui/index.ts +15 -0
  77. package/src/context-ui/types.ts +43 -0
  78. package/src/design-tweaks/index.ts +1 -0
  79. package/src/design-tweaks/visual-style-controls.tsx +1084 -0
  80. package/src/editor/BubbleToolbar.tsx +253 -0
  81. package/src/editor/CodeBlockNode.tsx +254 -0
  82. package/src/editor/DragHandle.spec.ts +588 -0
  83. package/src/editor/DragHandle.ts +1437 -0
  84. package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
  85. package/src/editor/ImageExtension.ts +306 -0
  86. package/src/editor/RegistryBlockContext.tsx +107 -0
  87. package/src/editor/RegistryBlockNode.spec.ts +279 -0
  88. package/src/editor/RegistryBlockNode.tsx +652 -0
  89. package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
  90. package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
  91. package/src/editor/RichMarkdownEditor.tsx +81 -0
  92. package/src/editor/RunId.ts +61 -0
  93. package/src/editor/SharedRichEditor.tsx +321 -0
  94. package/src/editor/SlashCommandMenu.tsx +310 -0
  95. package/src/editor/disableHistory.spec.ts +85 -0
  96. package/src/editor/extensions.ts +402 -0
  97. package/src/editor/gfmDoc.spec.ts +230 -0
  98. package/src/editor/gfmDoc.ts +92 -0
  99. package/src/editor/index.ts +93 -0
  100. package/src/editor/registrySlashCommands.ts +131 -0
  101. package/src/editor/structuralReorderUndo.spec.ts +144 -0
  102. package/src/editor/surgical-apply.spec.ts +279 -0
  103. package/src/editor/surgical-apply.ts +192 -0
  104. package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
  105. package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
  106. package/src/editor/useCollabReconcile.ts +589 -0
  107. package/src/editor.css +510 -0
  108. package/src/hooks/index.ts +2 -0
  109. package/src/hooks/use-mobile.tsx +21 -0
  110. package/src/hooks/use-toast.ts +188 -0
  111. package/src/index.ts +8 -0
  112. package/src/onboarding/index.ts +1 -0
  113. package/src/provider.tsx +47 -0
  114. package/src/sharing/VisibilityBadge.spec.ts +96 -0
  115. package/src/sharing/VisibilityBadge.tsx +37 -0
  116. package/src/sharing/index.ts +4 -0
  117. package/src/styles.css +21 -0
  118. package/src/ui/accordion.tsx +56 -0
  119. package/src/ui/alert-dialog.tsx +141 -0
  120. package/src/ui/alert.tsx +59 -0
  121. package/src/ui/aspect-ratio.tsx +5 -0
  122. package/src/ui/avatar.tsx +48 -0
  123. package/src/ui/badge.tsx +37 -0
  124. package/src/ui/breadcrumb.tsx +115 -0
  125. package/src/ui/button.tsx +78 -0
  126. package/src/ui/calendar.tsx +214 -0
  127. package/src/ui/card.tsx +86 -0
  128. package/src/ui/carousel.tsx +260 -0
  129. package/src/ui/chart.tsx +375 -0
  130. package/src/ui/checkbox.tsx +28 -0
  131. package/src/ui/collapsible.tsx +9 -0
  132. package/src/ui/command.spec.tsx +51 -0
  133. package/src/ui/command.tsx +174 -0
  134. package/src/ui/context-menu.tsx +202 -0
  135. package/src/ui/date-picker.tsx +64 -0
  136. package/src/ui/dialog.spec.tsx +43 -0
  137. package/src/ui/dialog.tsx +146 -0
  138. package/src/ui/drawer.tsx +126 -0
  139. package/src/ui/dropdown-menu.tsx +218 -0
  140. package/src/ui/form.tsx +177 -0
  141. package/src/ui/hover-card.tsx +27 -0
  142. package/src/ui/index.ts +52 -0
  143. package/src/ui/input-otp.tsx +69 -0
  144. package/src/ui/input.tsx +22 -0
  145. package/src/ui/label.tsx +24 -0
  146. package/src/ui/logo.tsx +36 -0
  147. package/src/ui/menubar.tsx +235 -0
  148. package/src/ui/navigation-menu.tsx +128 -0
  149. package/src/ui/pagination.tsx +121 -0
  150. package/src/ui/popover.tsx +44 -0
  151. package/src/ui/progress.tsx +26 -0
  152. package/src/ui/radio-group.tsx +42 -0
  153. package/src/ui/resizable.tsx +43 -0
  154. package/src/ui/scroll-area.tsx +46 -0
  155. package/src/ui/select.tsx +158 -0
  156. package/src/ui/separator.tsx +29 -0
  157. package/src/ui/sheet.tsx +160 -0
  158. package/src/ui/sidebar.tsx +777 -0
  159. package/src/ui/skeleton.tsx +18 -0
  160. package/src/ui/slider.tsx +26 -0
  161. package/src/ui/sonner.tsx +58 -0
  162. package/src/ui/spinner.tsx +17 -0
  163. package/src/ui/switch.tsx +27 -0
  164. package/src/ui/table.tsx +117 -0
  165. package/src/ui/tabs.tsx +53 -0
  166. package/src/ui/textarea.tsx +23 -0
  167. package/src/ui/toast.tsx +127 -0
  168. package/src/ui/toaster.tsx +33 -0
  169. package/src/ui/toggle-group.tsx +59 -0
  170. package/src/ui/toggle.tsx +43 -0
  171. package/src/ui/tooltip.tsx +53 -0
  172. package/src/ui/use-toast.ts +3 -0
  173. package/src/utils.ts +8 -0
@@ -0,0 +1,469 @@
1
+ import {
2
+ IconDots,
3
+ IconPencil,
4
+ IconPinned,
5
+ IconPinnedOff,
6
+ IconSearch,
7
+ IconTrash,
8
+ } from "@tabler/icons-react";
9
+ import React, { useEffect, useRef, useState } from "react";
10
+
11
+ import { cn } from "../utils.js";
12
+
13
+ /** A single row in a chat history list. Purely data — no fetching, no
14
+ * assistant-ui context. Formatting (relative time, "Active"/"Open" labels,
15
+ * status spinners) is the caller's responsibility; hand in the already
16
+ * rendered node via `timestamp`. */
17
+ export interface ChatHistoryItem {
18
+ id: string;
19
+ title: React.ReactNode;
20
+ /** Plain-text form of `title`, used to seed the inline rename input.
21
+ * Only needed when `onRename` is supplied. */
22
+ titleText?: string;
23
+ /** Secondary line — e.g. a message preview. */
24
+ subtitle?: React.ReactNode;
25
+ /** Tertiary line — e.g. a scope/folder label. */
26
+ detail?: React.ReactNode;
27
+ /** Pre-formatted trailing value: a relative time, "Active"/"Open", or a
28
+ * status node (spinner, unread dot). This component does not format
29
+ * dates or compute status itself. */
30
+ timestamp?: React.ReactNode;
31
+ pinned?: boolean;
32
+ disabled?: boolean;
33
+ }
34
+
35
+ export interface ChatHistorySection {
36
+ id: string;
37
+ label?: React.ReactNode;
38
+ items: ChatHistoryItem[];
39
+ }
40
+
41
+ export interface ChatHistoryListLabels {
42
+ options: string | ((item: ChatHistoryItem) => string);
43
+ renameInput: string | ((item: ChatHistoryItem) => string);
44
+ rename: string;
45
+ pin: string;
46
+ unpin: string;
47
+ delete: string;
48
+ }
49
+
50
+ export interface ChatHistoryListProps {
51
+ /** Flat item list. Ignored when `sections` is provided. */
52
+ items?: ChatHistoryItem[];
53
+ /** Grouped item list (e.g. "This deck" / "All chats", or pinned/recent). */
54
+ sections?: ChatHistorySection[];
55
+ /** Id of the currently active/selected item — highlighted in the list. */
56
+ activeId?: string | null;
57
+ onSelect: (id: string) => void;
58
+ /** Fired on double-click / explicit "open" gesture, distinct from select. */
59
+ onOpen?: (id: string) => void;
60
+ /** Presence enables the pin/unpin row action. */
61
+ onTogglePin?: (id: string) => void;
62
+ /** Presence enables the inline "Rename" row action. */
63
+ onRename?: (id: string, nextTitle: string) => void;
64
+ /** Optional character limit for the inline rename input. */
65
+ renameMaxLength?: number;
66
+ /** Presence enables the "Delete" row action. */
67
+ onDelete?: (id: string) => void;
68
+ /** Escape hatch: fully custom row action menu content, replacing the
69
+ * built-in rename/pin/delete items. Still gated by the trigger button,
70
+ * which shows whenever this or any of the on* callbacks above is set. */
71
+ renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
72
+ /** App-specific actions inserted after pin and before delete. Call
73
+ * `closeMenu` before starting the action. */
74
+ renderAdditionalRowActions?: (
75
+ item: ChatHistoryItem,
76
+ closeMenu: () => void,
77
+ ) => React.ReactNode;
78
+
79
+ /** Controlled search input. Omit `onSearchChange` to hide the search box
80
+ * entirely and let the host render its own (results are always supplied
81
+ * by the caller via `items`/`sections` either way). */
82
+ searchValue?: string;
83
+ onSearchChange?: (value: string) => void;
84
+ searchPlaceholder?: string;
85
+ searchInputRef?: React.Ref<HTMLInputElement>;
86
+
87
+ /** True while results are being fetched — shows `loadingLabel` instead of
88
+ * the list. */
89
+ loading?: boolean;
90
+ loadingLabel?: React.ReactNode;
91
+ /** Shown instead of the list when set (takes priority over `loading`). */
92
+ error?: React.ReactNode;
93
+ emptyLabel?: React.ReactNode;
94
+ /** Shown instead of `emptyLabel` when `searchValue` is non-empty. */
95
+ emptySearchLabel?: React.ReactNode;
96
+
97
+ /** Optional localized labels for row actions and their accessible controls. */
98
+ labels?: Partial<ChatHistoryListLabels>;
99
+
100
+ /** Rendered after the list, inside the scroll container (e.g. a
101
+ * "Load older chats" button). */
102
+ footer?: React.ReactNode;
103
+
104
+ /** "popover" (default) matches core's compact HistoryPopover list.
105
+ * "rail" is a denser variant with a solid active background, closer to
106
+ * a sidebar run rail. */
107
+ variant?: "popover" | "rail";
108
+ className?: string;
109
+ listClassName?: string;
110
+ }
111
+
112
+ const DEFAULT_LABELS: ChatHistoryListLabels = {
113
+ options: "Chat options",
114
+ renameInput: "Rename chat",
115
+ rename: "Rename",
116
+ pin: "Pin to top",
117
+ unpin: "Unpin from top",
118
+ delete: "Delete",
119
+ };
120
+
121
+ /**
122
+ * Presentational chat history list shared by the Core `HistoryPopover`
123
+ * and Agent-Native Code's run rail
124
+ * (code-agents-ui). Styling is driven by stable `an-chat-history*` class
125
+ * names (see `@agent-native/toolkit/chat-history.css`) rather than Tailwind utilities
126
+ * so the same component renders correctly in a Tailwind host and in
127
+ * code-agents-ui's plain-CSS host.
128
+ *
129
+ * This component owns no data: search filtering, pin/rename persistence,
130
+ * and time formatting all stay with the caller. It only renders whatever
131
+ * `items`/`sections` it is given, plus the optional search box and
132
+ * loading/empty/error states.
133
+ */
134
+ export function ChatHistoryList({
135
+ items,
136
+ sections,
137
+ activeId = null,
138
+ onSelect,
139
+ onOpen,
140
+ onTogglePin,
141
+ onRename,
142
+ renameMaxLength,
143
+ onDelete,
144
+ renderRowActions,
145
+ renderAdditionalRowActions,
146
+ searchValue,
147
+ onSearchChange,
148
+ searchPlaceholder = "Search chats...",
149
+ searchInputRef,
150
+ loading = false,
151
+ loadingLabel = "Searching...",
152
+ error,
153
+ emptyLabel = "No chats yet",
154
+ emptySearchLabel = "No matching chats",
155
+ labels,
156
+ footer,
157
+ variant = "popover",
158
+ className,
159
+ listClassName,
160
+ }: ChatHistoryListProps) {
161
+ const resolvedLabels: ChatHistoryListLabels = {
162
+ options: labels?.options ?? DEFAULT_LABELS.options,
163
+ renameInput: labels?.renameInput ?? DEFAULT_LABELS.renameInput,
164
+ rename: labels?.rename ?? DEFAULT_LABELS.rename,
165
+ pin: labels?.pin ?? DEFAULT_LABELS.pin,
166
+ unpin: labels?.unpin ?? DEFAULT_LABELS.unpin,
167
+ delete: labels?.delete ?? DEFAULT_LABELS.delete,
168
+ };
169
+ const resolvedSections: ChatHistorySection[] =
170
+ sections ?? (items ? [{ id: "default", items }] : []);
171
+ const totalCount = resolvedSections.reduce(
172
+ (sum, section) => sum + section.items.length,
173
+ 0,
174
+ );
175
+ const hasSearchValue = Boolean(searchValue?.trim());
176
+
177
+ return (
178
+ <div
179
+ className={cn(
180
+ "an-chat-history",
181
+ variant === "rail" && "an-chat-history--rail",
182
+ className,
183
+ )}
184
+ data-agent-native="chat-history-list"
185
+ >
186
+ {onSearchChange && (
187
+ <div className="an-chat-history__search">
188
+ <IconSearch size={13} className="an-chat-history__search-icon" />
189
+ <input
190
+ ref={searchInputRef}
191
+ type="text"
192
+ value={searchValue ?? ""}
193
+ onChange={(event) => onSearchChange(event.target.value)}
194
+ placeholder={searchPlaceholder}
195
+ aria-label={searchPlaceholder}
196
+ className="an-chat-history__search-input"
197
+ />
198
+ </div>
199
+ )}
200
+ <div className={cn("an-chat-history__list", listClassName)}>
201
+ {error ? (
202
+ <div className="an-chat-history__state an-chat-history__state--error">
203
+ {error}
204
+ </div>
205
+ ) : loading ? (
206
+ <div className="an-chat-history__state">{loadingLabel}</div>
207
+ ) : totalCount === 0 ? (
208
+ <div className="an-chat-history__state">
209
+ {hasSearchValue ? emptySearchLabel : emptyLabel}
210
+ </div>
211
+ ) : (
212
+ resolvedSections.map(
213
+ (section) =>
214
+ section.items.length > 0 && (
215
+ <div key={section.id} className="an-chat-history__section">
216
+ {section.label && (
217
+ <div className="an-chat-history__section-label">
218
+ {section.label}
219
+ </div>
220
+ )}
221
+ {section.items.map((item) => (
222
+ <ChatHistoryRow
223
+ key={item.id}
224
+ item={item}
225
+ active={item.id === activeId}
226
+ onSelect={onSelect}
227
+ onOpen={onOpen}
228
+ onTogglePin={onTogglePin}
229
+ onRename={onRename}
230
+ renameMaxLength={renameMaxLength}
231
+ onDelete={onDelete}
232
+ renderRowActions={renderRowActions}
233
+ renderAdditionalRowActions={renderAdditionalRowActions}
234
+ labels={resolvedLabels}
235
+ />
236
+ ))}
237
+ </div>
238
+ ),
239
+ )
240
+ )}
241
+ {footer}
242
+ </div>
243
+ </div>
244
+ );
245
+ }
246
+
247
+ function ChatHistoryRow({
248
+ item,
249
+ active,
250
+ onSelect,
251
+ onOpen,
252
+ onTogglePin,
253
+ onRename,
254
+ renameMaxLength,
255
+ onDelete,
256
+ renderRowActions,
257
+ renderAdditionalRowActions,
258
+ labels,
259
+ }: {
260
+ item: ChatHistoryItem;
261
+ active: boolean;
262
+ onSelect: (id: string) => void;
263
+ onOpen?: (id: string) => void;
264
+ onTogglePin?: (id: string) => void;
265
+ onRename?: (id: string, nextTitle: string) => void;
266
+ renameMaxLength?: number;
267
+ onDelete?: (id: string) => void;
268
+ renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
269
+ renderAdditionalRowActions?: (
270
+ item: ChatHistoryItem,
271
+ closeMenu: () => void,
272
+ ) => React.ReactNode;
273
+ labels: ChatHistoryListLabels;
274
+ }) {
275
+ const [isRenaming, setIsRenaming] = useState(false);
276
+ const [draftTitle, setDraftTitle] = useState("");
277
+ const [menuOpen, setMenuOpen] = useState(false);
278
+ const renameInputRef = useRef<HTMLInputElement | null>(null);
279
+ const menuRef = useRef<HTMLDivElement | null>(null);
280
+
281
+ const hasMenu = Boolean(
282
+ onTogglePin ||
283
+ onRename ||
284
+ onDelete ||
285
+ renderRowActions ||
286
+ renderAdditionalRowActions,
287
+ );
288
+
289
+ useEffect(() => {
290
+ if (!menuOpen) return;
291
+ function handlePointerDown(event: PointerEvent) {
292
+ if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
293
+ setMenuOpen(false);
294
+ }
295
+ }
296
+ function handleKeyDown(event: KeyboardEvent) {
297
+ if (event.key === "Escape") setMenuOpen(false);
298
+ }
299
+ document.addEventListener("pointerdown", handlePointerDown);
300
+ document.addEventListener("keydown", handleKeyDown);
301
+ return () => {
302
+ document.removeEventListener("pointerdown", handlePointerDown);
303
+ document.removeEventListener("keydown", handleKeyDown);
304
+ };
305
+ }, [menuOpen]);
306
+
307
+ function startRename() {
308
+ setDraftTitle(item.titleText ?? "");
309
+ setIsRenaming(true);
310
+ setMenuOpen(false);
311
+ window.requestAnimationFrame(() => {
312
+ renameInputRef.current?.select();
313
+ });
314
+ }
315
+
316
+ function commitRename() {
317
+ const trimmed = draftTitle.trim();
318
+ setIsRenaming(false);
319
+ if (trimmed && trimmed !== item.titleText) {
320
+ onRename?.(item.id, trimmed);
321
+ }
322
+ }
323
+
324
+ function handleRenameKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
325
+ if (event.key === "Enter") {
326
+ event.preventDefault();
327
+ commitRename();
328
+ } else if (event.key === "Escape") {
329
+ event.preventDefault();
330
+ setIsRenaming(false);
331
+ }
332
+ }
333
+
334
+ const optionsLabel =
335
+ typeof labels.options === "function"
336
+ ? labels.options(item)
337
+ : labels.options;
338
+ const renameInputLabel =
339
+ typeof labels.renameInput === "function"
340
+ ? labels.renameInput(item)
341
+ : labels.renameInput;
342
+
343
+ return (
344
+ <div
345
+ className={cn(
346
+ "an-chat-history-row",
347
+ active && "an-chat-history-row--active",
348
+ item.pinned && "an-chat-history-row--pinned",
349
+ isRenaming && "an-chat-history-row--renaming",
350
+ item.disabled && "an-chat-history-row--disabled",
351
+ )}
352
+ >
353
+ {isRenaming ? (
354
+ <div className="an-chat-history-row__rename">
355
+ <input
356
+ ref={renameInputRef}
357
+ className="an-chat-history-row__rename-input"
358
+ value={draftTitle}
359
+ maxLength={renameMaxLength}
360
+ onChange={(event) => setDraftTitle(event.target.value)}
361
+ onKeyDown={handleRenameKeyDown}
362
+ onBlur={commitRename}
363
+ autoFocus
364
+ aria-label={renameInputLabel}
365
+ />
366
+ </div>
367
+ ) : (
368
+ <button
369
+ type="button"
370
+ className="an-chat-history-row__button"
371
+ onClick={() => !item.disabled && onSelect(item.id)}
372
+ onDoubleClick={() => !item.disabled && onOpen?.(item.id)}
373
+ disabled={item.disabled}
374
+ >
375
+ <div className="an-chat-history-row__topline">
376
+ <span className="an-chat-history-row__title">{item.title}</span>
377
+ {item.timestamp != null && (
378
+ <span className="an-chat-history-row__timestamp">
379
+ {item.timestamp}
380
+ </span>
381
+ )}
382
+ </div>
383
+ {item.subtitle != null && (
384
+ <div className="an-chat-history-row__subtitle">{item.subtitle}</div>
385
+ )}
386
+ {item.detail != null && (
387
+ <div className="an-chat-history-row__detail">{item.detail}</div>
388
+ )}
389
+ </button>
390
+ )}
391
+
392
+ {!isRenaming && hasMenu && (
393
+ <div className="an-chat-history-row__menu" ref={menuRef}>
394
+ <button
395
+ type="button"
396
+ className={cn(
397
+ "an-chat-history-row__menu-trigger",
398
+ item.pinned && "an-chat-history-row__menu-trigger--pinned",
399
+ )}
400
+ aria-label={optionsLabel}
401
+ aria-haspopup="menu"
402
+ aria-expanded={menuOpen}
403
+ onClick={() => setMenuOpen((open) => !open)}
404
+ >
405
+ {item.pinned ? (
406
+ <IconPinned size={13} strokeWidth={1.8} />
407
+ ) : (
408
+ <IconDots size={14} strokeWidth={1.8} />
409
+ )}
410
+ </button>
411
+ {menuOpen && (
412
+ <div className="an-chat-history-row__menu-content" role="menu">
413
+ {renderRowActions ? (
414
+ renderRowActions(item)
415
+ ) : (
416
+ <>
417
+ {onRename && (
418
+ <button
419
+ type="button"
420
+ role="menuitem"
421
+ className="an-chat-history-row__menu-item"
422
+ onClick={startRename}
423
+ >
424
+ <IconPencil size={13} strokeWidth={1.8} />
425
+ <span>{labels.rename}</span>
426
+ </button>
427
+ )}
428
+ {onTogglePin && (
429
+ <button
430
+ type="button"
431
+ role="menuitem"
432
+ className="an-chat-history-row__menu-item"
433
+ onClick={() => {
434
+ setMenuOpen(false);
435
+ onTogglePin(item.id);
436
+ }}
437
+ >
438
+ {item.pinned ? (
439
+ <IconPinnedOff size={13} strokeWidth={1.8} />
440
+ ) : (
441
+ <IconPinned size={13} strokeWidth={1.8} />
442
+ )}
443
+ <span>{item.pinned ? labels.unpin : labels.pin}</span>
444
+ </button>
445
+ )}
446
+ {renderAdditionalRowActions?.(item, () => setMenuOpen(false))}
447
+ {onDelete && (
448
+ <button
449
+ type="button"
450
+ role="menuitem"
451
+ className="an-chat-history-row__menu-item an-chat-history-row__menu-item--danger"
452
+ onClick={() => {
453
+ setMenuOpen(false);
454
+ onDelete(item.id);
455
+ }}
456
+ >
457
+ <IconTrash size={13} strokeWidth={1.8} />
458
+ <span>{labels.delete}</span>
459
+ </button>
460
+ )}
461
+ </>
462
+ )}
463
+ </div>
464
+ )}
465
+ </div>
466
+ )}
467
+ </div>
468
+ );
469
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ ChatHistoryList,
3
+ type ChatHistoryItem,
4
+ type ChatHistoryListLabels,
5
+ type ChatHistoryListProps,
6
+ type ChatHistorySection,
7
+ } from "./ChatHistoryList.js";