@agent-native/toolkit 0.211.0 → 0.212.0-nightly-20261021010049

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 (143) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  3. package/dist/app/i18n/catalogs/ar-SA.js +23 -0
  4. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  5. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  6. package/dist/app/i18n/catalogs/de-DE.js +23 -0
  7. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  8. package/dist/app/i18n/catalogs/en-US.d.ts +23 -0
  9. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  10. package/dist/app/i18n/catalogs/en-US.js +23 -0
  11. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  12. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  13. package/dist/app/i18n/catalogs/es-ES.js +23 -0
  14. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  15. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  16. package/dist/app/i18n/catalogs/fr-FR.js +23 -0
  17. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  18. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  19. package/dist/app/i18n/catalogs/hi-IN.js +23 -0
  20. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  21. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  22. package/dist/app/i18n/catalogs/ja-JP.js +23 -0
  23. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  24. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  25. package/dist/app/i18n/catalogs/ko-KR.js +23 -0
  26. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  27. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/pt-BR.js +23 -0
  29. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  30. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/zh-CN.js +23 -0
  32. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  33. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/zh-TW.js +23 -0
  35. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  36. package/dist/app/sharing/OwnershipFilter.d.ts +8 -0
  37. package/dist/app/sharing/OwnershipFilter.d.ts.map +1 -0
  38. package/dist/app/sharing/OwnershipFilter.js +20 -0
  39. package/dist/app/sharing/OwnershipFilter.js.map +1 -0
  40. package/dist/app/sharing/ShareButton.d.ts +43 -1
  41. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  42. package/dist/app/sharing/ShareButton.js +102 -63
  43. package/dist/app/sharing/ShareButton.js.map +1 -1
  44. package/dist/app/sharing/ShareDialog.d.ts +3 -1
  45. package/dist/app/sharing/ShareDialog.d.ts.map +1 -1
  46. package/dist/app/sharing/ShareDialog.js +18 -12
  47. package/dist/app/sharing/ShareDialog.js.map +1 -1
  48. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts +20 -0
  49. package/dist/app/sharing/ShareVisibilityConfirmDialog.d.ts.map +1 -0
  50. package/dist/app/sharing/ShareVisibilityConfirmDialog.js +57 -0
  51. package/dist/app/sharing/ShareVisibilityConfirmDialog.js.map +1 -0
  52. package/dist/app/sharing/VisibilityBadge.d.ts +8 -1
  53. package/dist/app/sharing/VisibilityBadge.d.ts.map +1 -1
  54. package/dist/app/sharing/VisibilityBadge.js +16 -1
  55. package/dist/app/sharing/VisibilityBadge.js.map +1 -1
  56. package/dist/app/sharing/index.d.ts +2 -0
  57. package/dist/app/sharing/index.d.ts.map +1 -1
  58. package/dist/app/sharing/index.js +2 -0
  59. package/dist/app/sharing/index.js.map +1 -1
  60. package/dist/chat-history/ChatHistoryList.d.ts +19 -0
  61. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  62. package/dist/chat-history/ChatHistoryList.js +19 -7
  63. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  64. package/dist/chat-history/ChatHistoryRail.d.ts +55 -8
  65. package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
  66. package/dist/chat-history/ChatHistoryRail.js +33 -7
  67. package/dist/chat-history/ChatHistoryRail.js.map +1 -1
  68. package/dist/chat-history/index.d.ts +3 -3
  69. package/dist/chat-history/index.d.ts.map +1 -1
  70. package/dist/chat-history/index.js +1 -1
  71. package/dist/chat-history/index.js.map +1 -1
  72. package/dist/chat-history/useChatHistoryRailController.d.ts +44 -0
  73. package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -1
  74. package/dist/chat-history/useChatHistoryRailController.js +46 -0
  75. package/dist/chat-history/useChatHistoryRailController.js.map +1 -1
  76. package/dist/chat-history.css +29 -0
  77. package/dist/composer/TiptapComposer.d.ts +7 -0
  78. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  79. package/dist/composer/TiptapComposer.js +15 -2
  80. package/dist/composer/TiptapComposer.js.map +1 -1
  81. package/dist/sharing/OwnershipFilter.d.ts +21 -0
  82. package/dist/sharing/OwnershipFilter.d.ts.map +1 -0
  83. package/dist/sharing/OwnershipFilter.js +30 -0
  84. package/dist/sharing/OwnershipFilter.js.map +1 -0
  85. package/dist/sharing/VisibilityBadge.d.ts +12 -2
  86. package/dist/sharing/VisibilityBadge.d.ts.map +1 -1
  87. package/dist/sharing/VisibilityBadge.js +13 -4
  88. package/dist/sharing/VisibilityBadge.js.map +1 -1
  89. package/dist/sharing/index.d.ts +2 -1
  90. package/dist/sharing/index.d.ts.map +1 -1
  91. package/dist/sharing/index.js +2 -1
  92. package/dist/sharing/index.js.map +1 -1
  93. package/dist/ui/alert-dialog.d.ts +2 -0
  94. package/dist/ui/alert-dialog.d.ts.map +1 -1
  95. package/dist/ui/alert-dialog.js +1 -1
  96. package/dist/ui/alert-dialog.js.map +1 -1
  97. package/dist/ui/tooltip.d.ts +6 -1
  98. package/dist/ui/tooltip.d.ts.map +1 -1
  99. package/dist/ui/tooltip.js +13 -1
  100. package/dist/ui/tooltip.js.map +1 -1
  101. package/package.json +4 -4
  102. package/src/app/i18n/catalogs/ar-SA.ts +26 -0
  103. package/src/app/i18n/catalogs/de-DE.ts +25 -0
  104. package/src/app/i18n/catalogs/en-US.ts +25 -0
  105. package/src/app/i18n/catalogs/es-ES.ts +25 -0
  106. package/src/app/i18n/catalogs/fr-FR.ts +25 -0
  107. package/src/app/i18n/catalogs/hi-IN.ts +25 -0
  108. package/src/app/i18n/catalogs/ja-JP.ts +25 -0
  109. package/src/app/i18n/catalogs/ko-KR.ts +25 -0
  110. package/src/app/i18n/catalogs/pt-BR.ts +25 -0
  111. package/src/app/i18n/catalogs/zh-CN.ts +25 -0
  112. package/src/app/i18n/catalogs/zh-TW.ts +25 -0
  113. package/src/app/sharing/OwnershipFilter.tsx +35 -0
  114. package/src/app/sharing/ShareButton.tsx +384 -259
  115. package/src/app/sharing/ShareDialog.spec.tsx +50 -0
  116. package/src/app/sharing/ShareDialog.tsx +51 -15
  117. package/src/app/sharing/ShareVisibilityConfirmDialog.spec.tsx +138 -0
  118. package/src/app/sharing/ShareVisibilityConfirmDialog.tsx +124 -0
  119. package/src/app/sharing/ShareVisibilityKeyboard.spec.tsx +244 -0
  120. package/src/app/sharing/VisibilityBadge.spec.tsx +65 -0
  121. package/src/app/sharing/VisibilityBadge.tsx +31 -3
  122. package/src/app/sharing/index.ts +2 -0
  123. package/src/app/sharing/share-visibility-mutation-lifecycle.spec.tsx +1107 -0
  124. package/src/app/sharing/useShareButtonController.spec.tsx +226 -37
  125. package/src/app/sharing/useShareDialogController.spec.tsx +357 -9
  126. package/src/chat-history/ChatHistoryList.spec.tsx +157 -0
  127. package/src/chat-history/ChatHistoryList.tsx +104 -10
  128. package/src/chat-history/ChatHistoryRail.spec.tsx +50 -1
  129. package/src/chat-history/ChatHistoryRail.tsx +177 -26
  130. package/src/chat-history/ChatHistoryRailSections.spec.tsx +410 -0
  131. package/src/chat-history/index.ts +8 -0
  132. package/src/chat-history/useChatHistoryRailController.ts +94 -0
  133. package/src/chat-history/useChatHistoryRailSectionsController.spec.tsx +133 -0
  134. package/src/chat-history.css +29 -0
  135. package/src/composer/TiptapComposer.spec.ts +19 -0
  136. package/src/composer/TiptapComposer.tsx +26 -5
  137. package/src/sharing/OwnershipFilter.spec.tsx +108 -0
  138. package/src/sharing/OwnershipFilter.tsx +103 -0
  139. package/src/sharing/VisibilityBadge.spec.ts +53 -35
  140. package/src/sharing/VisibilityBadge.tsx +23 -3
  141. package/src/sharing/index.ts +11 -0
  142. package/src/ui/alert-dialog.tsx +7 -2
  143. package/src/ui/tooltip.tsx +22 -1
@@ -9,23 +9,48 @@ import {
9
9
  } from "@tabler/icons-react";
10
10
  import React, { useMemo, useRef, useState } from "react";
11
11
 
12
+ import { Skeleton } from "../design-system/components.js";
12
13
  import { cn } from "../utils.js";
13
14
 
15
+ /** How the viewer got access to a chat; the app supplies the translated copy. */
16
+ export type ChatHistoryAccessBasis =
17
+ | "owner"
18
+ | "direct"
19
+ | "organization"
20
+ | "public";
21
+
14
22
  export interface ChatHistoryItem {
15
23
  id: string;
16
24
  title: React.ReactNode;
17
25
  titleText?: string;
18
26
  subtitle?: React.ReactNode;
19
27
  detail?: React.ReactNode;
28
+ /** Already-translated owner name, shown on shared rows. */
29
+ ownerLabel?: React.ReactNode;
30
+ /** Already-translated access description, e.g. "Shared with you". */
31
+ accessLabel?: React.ReactNode;
32
+ accessBasis?: ChatHistoryAccessBasis;
20
33
  timestamp?: React.ReactNode;
21
34
  pinned?: boolean;
22
35
  disabled?: boolean;
36
+ /**
37
+ * Presentation only: drops the row menu and context menu while the row stays
38
+ * selectable. The app decides which rows (e.g. not owned) get no actions.
39
+ */
40
+ hideActions?: boolean;
23
41
  }
24
42
 
25
43
  export interface ChatHistorySection {
26
44
  id: string;
27
45
  label?: React.ReactNode;
28
46
  items: ChatHistoryItem[];
47
+ /** Section-level states; the section stays visible while any is set. */
48
+ /** Initial load: skeleton rows until the first rows arrive. */
49
+ loading?: boolean;
50
+ /** Shown above any rows already loaded: a failed load never reads as empty. */
51
+ error?: React.ReactNode;
52
+ emptyLabel?: React.ReactNode;
53
+ footer?: React.ReactNode;
29
54
  }
30
55
 
31
56
  export interface ChatHistoryListLabels {
@@ -134,6 +159,9 @@ export function ChatHistoryList({
134
159
  0,
135
160
  );
136
161
  const hasSearchValue = Boolean(searchValue?.trim());
162
+ const hasSectionState = resolvedSections.some((section) =>
163
+ sectionHasState(section, hasSearchValue),
164
+ );
137
165
 
138
166
  return (
139
167
  <div
@@ -165,20 +193,54 @@ export function ChatHistoryList({
165
193
  </div>
166
194
  ) : loading ? (
167
195
  <div className="an-chat-history__state">{loadingLabel}</div>
168
- ) : totalCount === 0 ? (
196
+ ) : totalCount === 0 && !hasSectionState ? (
169
197
  <div className="an-chat-history__state">
170
198
  {hasSearchValue ? emptySearchLabel : emptyLabel}
171
199
  </div>
172
200
  ) : (
173
201
  resolvedSections.map(
174
202
  (section) =>
175
- section.items.length > 0 && (
176
- <div key={section.id} className="an-chat-history__section">
203
+ (section.items.length > 0 ||
204
+ sectionHasState(section, hasSearchValue)) && (
205
+ <div
206
+ key={section.id}
207
+ className="an-chat-history__section"
208
+ data-section-id={section.id}
209
+ >
177
210
  {section.label && (
178
211
  <div className="an-chat-history__section-label">
179
212
  {section.label}
180
213
  </div>
181
214
  )}
215
+ {section.error ? (
216
+ <div
217
+ className="an-chat-history__state an-chat-history__state--error"
218
+ role="alert"
219
+ >
220
+ {section.error}
221
+ </div>
222
+ ) : section.loading && section.items.length === 0 ? (
223
+ <div
224
+ className="an-chat-history__skeletons"
225
+ aria-busy="true"
226
+ data-testid="chat-history-section-skeleton"
227
+ >
228
+ {["100%", "80%", "60%"].map((width) => (
229
+ <Skeleton
230
+ key={width}
231
+ shape="line"
232
+ width={width}
233
+ height={12}
234
+ />
235
+ ))}
236
+ </div>
237
+ ) : section.items.length === 0 &&
238
+ !hasSearchValue &&
239
+ section.emptyLabel ? (
240
+ <div className="an-chat-history__state">
241
+ {section.emptyLabel}
242
+ </div>
243
+ ) : null}
182
244
  {section.items.map((item) => (
183
245
  <ChatHistoryRow
184
246
  key={item.id}
@@ -195,6 +257,7 @@ export function ChatHistoryList({
195
257
  labels={resolvedLabels}
196
258
  />
197
259
  ))}
260
+ {section.footer}
198
261
  </div>
199
262
  ),
200
263
  )
@@ -205,6 +268,18 @@ export function ChatHistoryList({
205
268
  );
206
269
  }
207
270
 
271
+ function sectionHasState(
272
+ section: ChatHistorySection,
273
+ searching: boolean,
274
+ ): boolean {
275
+ return Boolean(
276
+ section.loading ||
277
+ section.error ||
278
+ (!searching && section.emptyLabel) ||
279
+ section.footer,
280
+ );
281
+ }
282
+
208
283
  type ChatHistoryRowProps = {
209
284
  item: ChatHistoryItem;
210
285
  active: boolean;
@@ -240,13 +315,15 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
240
315
  const [menuOpen, setMenuOpen] = useState(false);
241
316
  const renameInputRef = useRef<HTMLInputElement | null>(null);
242
317
 
243
- const hasMenu = Boolean(
244
- onTogglePin ||
245
- onRename ||
246
- onDelete ||
247
- renderRowActions ||
248
- renderAdditionalRowActions,
249
- );
318
+ const hasMenu =
319
+ !item.hideActions &&
320
+ Boolean(
321
+ onTogglePin ||
322
+ onRename ||
323
+ onDelete ||
324
+ renderRowActions ||
325
+ renderAdditionalRowActions,
326
+ );
250
327
 
251
328
  function startRename() {
252
329
  setDraftTitle(item.titleText ?? "");
@@ -337,6 +414,23 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
337
414
  {item.detail != null && (
338
415
  <div className="an-chat-history-row__detail">{item.detail}</div>
339
416
  )}
417
+ {(item.ownerLabel != null || item.accessLabel != null) && (
418
+ <div
419
+ className="an-chat-history-row__access"
420
+ data-access-basis={item.accessBasis}
421
+ >
422
+ {item.ownerLabel != null && (
423
+ <span className="an-chat-history-row__owner">
424
+ {item.ownerLabel}
425
+ </span>
426
+ )}
427
+ {item.accessLabel != null && (
428
+ <span className="an-chat-history-row__access-label">
429
+ {item.accessLabel}
430
+ </span>
431
+ )}
432
+ </div>
433
+ )}
340
434
  </button>
341
435
  )}
342
436
 
@@ -8,7 +8,10 @@ import { ActionButton, IconButton } from "../design-system/components.js";
8
8
  import { defineDesignSystem } from "../design-system/definition.js";
9
9
  import { ToolkitProvider } from "../provider.js";
10
10
  import type { ChatHistoryItem } from "./ChatHistoryList.js";
11
- import { ChatHistoryRail } from "./ChatHistoryRail.js";
11
+ import {
12
+ ChatHistoryRail,
13
+ DefaultChatHistoryRailView,
14
+ } from "./ChatHistoryRail.js";
12
15
 
13
16
  const railLabels = {
14
17
  newChat: "New chat",
@@ -67,6 +70,24 @@ describe("ChatHistoryRail", () => {
67
70
  expect(container.querySelectorAll(".an-chat-history-row")).toHaveLength(5);
68
71
  });
69
72
 
73
+ it("keeps the flat rail's list-level empty state", () => {
74
+ act(() => {
75
+ root.render(
76
+ <ChatHistoryRail
77
+ items={[]}
78
+ onSelect={() => {}}
79
+ onNewChat={() => {}}
80
+ railLabels={railLabels}
81
+ emptyLabel="No chats yet"
82
+ />,
83
+ );
84
+ });
85
+
86
+ expect(
87
+ container.querySelector(".an-chat-history__state")?.textContent,
88
+ ).toBe("No chats yet");
89
+ });
90
+
70
91
  it("changes the disclosure glyph with its state, even when both labels match", () => {
71
92
  act(() => {
72
93
  root.render(
@@ -343,6 +364,34 @@ describe("ChatHistoryRail", () => {
343
364
  expect(onNewChat).toHaveBeenCalledOnce();
344
365
  });
345
366
 
367
+ it("renders the exported default view from the flat controller and listProps alone", () => {
368
+ const onNewChat = vi.fn();
369
+ act(() => {
370
+ root.render(
371
+ <ChatHistoryRail
372
+ items={makeItems(6)}
373
+ onSelect={() => {}}
374
+ onNewChat={onNewChat}
375
+ railLabels={railLabels}
376
+ renderRail={({ controller, listProps }) => (
377
+ <DefaultChatHistoryRailView
378
+ controller={controller}
379
+ listProps={listProps}
380
+ />
381
+ )}
382
+ />,
383
+ );
384
+ });
385
+
386
+ expect(container.querySelectorAll(".an-chat-history-row")).toHaveLength(5);
387
+ act(() =>
388
+ container
389
+ .querySelector<HTMLButtonElement>(".an-chat-history-rail__new-chat")
390
+ ?.click(),
391
+ );
392
+ expect(onNewChat).toHaveBeenCalledOnce();
393
+ });
394
+
346
395
  it("falls back to the default rail when a product renderer fails", () => {
347
396
  vi.spyOn(console, "error").mockImplementation(() => {});
348
397
  act(() => {
@@ -7,44 +7,180 @@ import {
7
7
  ChatHistoryList,
8
8
  type ChatHistoryItem,
9
9
  type ChatHistoryListProps,
10
+ type ChatHistorySection,
10
11
  } from "./ChatHistoryList.js";
11
12
  import {
12
13
  type ChatHistoryRailController,
14
+ type ChatHistoryRailSectionController,
15
+ type ChatHistoryRailSectionControllerLabels,
16
+ type ChatHistoryRailSectionInput,
13
17
  useChatHistoryRailController,
18
+ useChatHistoryRailSectionsController,
14
19
  } from "./useChatHistoryRailController.js";
15
20
 
16
21
  export interface ChatHistoryRailLabels {
17
22
  newChat: string;
18
23
  showMore: string;
19
24
  showLess: string;
25
+ /**
26
+ * Translated disclosure, load-more, and retry copy for every section.
27
+ * Required whenever the rail is given `sections`; there are no defaults.
28
+ */
29
+ sections?: ChatHistoryRailSectionControllerLabels;
20
30
  }
21
31
 
22
- export interface ChatHistoryRailProps extends Omit<
32
+ export interface ChatHistoryRailSection extends ChatHistoryRailSectionInput<ChatHistoryItem> {
33
+ label?: ReactNode;
34
+ /** Initial load: renders skeleton rows until the first rows arrive. */
35
+ loading?: boolean;
36
+ /** A failed load, shown above any rows already loaded. */
37
+ error?: ReactNode;
38
+ onRetry?: () => void;
39
+ emptyLabel?: ReactNode;
40
+ }
41
+
42
+ type ChatHistoryRailListProps = Omit<
23
43
  ChatHistoryListProps,
24
44
  "footer" | "items" | "sections" | "variant"
25
- > {
26
- items: ChatHistoryItem[];
45
+ >;
46
+
47
+ interface ChatHistoryRailBaseProps extends ChatHistoryRailListProps {
27
48
  onNewChat: () => void;
28
- railLabels: ChatHistoryRailLabels;
29
49
  previewCount?: number;
30
50
  expandedCount?: number;
31
51
  renderRail?: (context: ChatHistoryRailRenderContext) => ReactNode;
32
52
  }
33
53
 
54
+ interface ChatHistoryRailFlatProps {
55
+ /** One flat list with a single disclosure. */
56
+ items?: ChatHistoryItem[];
57
+ sections?: undefined;
58
+ showNewChat?: undefined;
59
+ railLabels: ChatHistoryRailLabels;
60
+ }
61
+
62
+ interface ChatHistoryRailSectionedProps {
63
+ /**
64
+ * Independent sections, each with its own preview, disclosure, paging, and
65
+ * loading/error/empty states. The app owns which rows each section holds.
66
+ */
67
+ sections: ChatHistoryRailSection[];
68
+ items?: undefined;
69
+ /**
70
+ * Set false when the surrounding chrome already owns New chat. `onNewChat`
71
+ * stays required and callable; only the rail's own button is dropped.
72
+ */
73
+ showNewChat?: boolean;
74
+ railLabels: ChatHistoryRailLabels & {
75
+ sections: ChatHistoryRailSectionControllerLabels;
76
+ };
77
+ }
78
+
79
+ export type ChatHistoryRailProps = ChatHistoryRailBaseProps &
80
+ (ChatHistoryRailFlatProps | ChatHistoryRailSectionedProps);
81
+
82
+ export interface ChatHistoryRailSectionRenderState {
83
+ section: ChatHistoryRailSection;
84
+ controller: ChatHistoryRailSectionController<ChatHistoryItem>;
85
+ }
86
+
34
87
  export interface ChatHistoryRailRenderContext {
35
88
  controller: ChatHistoryRailController<ChatHistoryItem>;
36
- listProps: Omit<
37
- ChatHistoryListProps,
38
- "footer" | "items" | "sections" | "variant"
39
- >;
89
+ /** Empty unless the rail was given `sections`. */
90
+ sections: ChatHistoryRailSectionRenderState[];
91
+ listProps: ChatHistoryRailListProps;
92
+ /** Sectioned rails only; defaults to true. */
93
+ showNewChat?: boolean;
94
+ }
95
+
96
+ /** `sections` stays optional so views written for the flat rail keep working. */
97
+ export type DefaultChatHistoryRailViewProps = Omit<
98
+ ChatHistoryRailRenderContext,
99
+ "sections"
100
+ > &
101
+ Partial<Pick<ChatHistoryRailRenderContext, "sections">>;
102
+
103
+ function SectionFooter({
104
+ section,
105
+ controller,
106
+ }: ChatHistoryRailSectionRenderState) {
107
+ const { labels, loadMore } = controller;
108
+ if (!controller.canExpand) return null;
109
+ return (
110
+ <div className="an-chat-history-rail__section-footer">
111
+ <ActionButton
112
+ type="button"
113
+ className="an-chat-history-rail__section-toggle"
114
+ emphasis="ghost"
115
+ size="compact"
116
+ trailingIcon={
117
+ controller.expanded ? (
118
+ <IconChevronUp size={13} strokeWidth={1.8} aria-hidden="true" />
119
+ ) : (
120
+ <IconChevronDown size={13} strokeWidth={1.8} aria-hidden="true" />
121
+ )
122
+ }
123
+ onPress={controller.toggleExpanded}
124
+ aria-expanded={controller.expanded}
125
+ data-section-id={section.id}
126
+ >
127
+ <span>{controller.expanded ? labels.showLess : labels.showAll}</span>
128
+ </ActionButton>
129
+ {loadMore && (
130
+ <ActionButton
131
+ type="button"
132
+ className="an-chat-history-rail__load-more"
133
+ emphasis="ghost"
134
+ size="compact"
135
+ pending={controller.loadingMore}
136
+ disabled={controller.loadingMore}
137
+ onPress={loadMore}
138
+ >
139
+ <span>
140
+ {controller.loadingMore ? labels.loadingMore : labels.loadMore}
141
+ </span>
142
+ </ActionButton>
143
+ )}
144
+ </div>
145
+ );
40
146
  }
41
147
 
42
- export type DefaultChatHistoryRailViewProps = ChatHistoryRailRenderContext;
148
+ function toListSection(
149
+ state: ChatHistoryRailSectionRenderState,
150
+ ): ChatHistorySection {
151
+ const { section, controller } = state;
152
+ return {
153
+ id: section.id,
154
+ label: section.label,
155
+ items: controller.visibleItems,
156
+ loading: section.loading,
157
+ error: section.error ? (
158
+ <>
159
+ <span>{section.error}</span>
160
+ {section.onRetry && (
161
+ <ActionButton
162
+ type="button"
163
+ className="an-chat-history-rail__retry"
164
+ emphasis="ghost"
165
+ size="compact"
166
+ onPress={section.onRetry}
167
+ >
168
+ <span>{controller.labels.retry}</span>
169
+ </ActionButton>
170
+ )}
171
+ </>
172
+ ) : undefined,
173
+ emptyLabel: section.emptyLabel,
174
+ footer: controller.canExpand ? <SectionFooter {...state} /> : undefined,
175
+ };
176
+ }
43
177
 
44
178
  export function DefaultChatHistoryRailView({
45
179
  controller,
180
+ sections = [],
46
181
  listProps,
47
- }: ChatHistoryRailRenderContext) {
182
+ showNewChat = true,
183
+ }: DefaultChatHistoryRailViewProps) {
48
184
  const {
49
185
  canExpand,
50
186
  disclosureLabel,
@@ -56,20 +192,22 @@ export function DefaultChatHistoryRailView({
56
192
  } = controller;
57
193
  const { className, emptyLabel, ...rest } = listProps;
58
194
 
59
- const footer = (
195
+ const footer = (showNewChat || canExpand) && (
60
196
  <div className="an-chat-history-rail__footer">
61
- <ActionButton
62
- type="button"
63
- className="an-chat-history-rail__new-chat"
64
- emphasis="ghost"
65
- size="compact"
66
- leadingIcon={
67
- <IconPlus size={13} strokeWidth={1.8} aria-hidden="true" />
68
- }
69
- onPress={onNewChat}
70
- >
71
- <span>{newChatLabel}</span>
72
- </ActionButton>
197
+ {showNewChat && (
198
+ <ActionButton
199
+ type="button"
200
+ className="an-chat-history-rail__new-chat"
201
+ emphasis="ghost"
202
+ size="compact"
203
+ leadingIcon={
204
+ <IconPlus size={13} strokeWidth={1.8} aria-hidden="true" />
205
+ }
206
+ onPress={onNewChat}
207
+ >
208
+ <span>{newChatLabel}</span>
209
+ </ActionButton>
210
+ )}
73
211
  {canExpand && (
74
212
  <IconButton
75
213
  type="button"
@@ -94,8 +232,10 @@ export function DefaultChatHistoryRailView({
94
232
  return (
95
233
  <ChatHistoryList
96
234
  {...rest}
97
- items={visibleItems}
98
- footer={footer}
235
+ {...(sections.length > 0
236
+ ? { sections: sections.map(toListSection) }
237
+ : { items: visibleItems })}
238
+ footer={footer || undefined}
99
239
  emptyLabel={emptyLabel ?? null}
100
240
  variant="rail"
101
241
  className={["an-chat-history-rail", className].filter(Boolean).join(" ")}
@@ -105,6 +245,8 @@ export function DefaultChatHistoryRailView({
105
245
 
106
246
  export function ChatHistoryRail({
107
247
  items,
248
+ sections: sectionInputs,
249
+ showNewChat,
108
250
  onNewChat,
109
251
  railLabels,
110
252
  previewCount = 5,
@@ -115,15 +257,24 @@ export function ChatHistoryRail({
115
257
  ...listProps
116
258
  }: ChatHistoryRailProps) {
117
259
  const controller = useChatHistoryRailController({
118
- items,
260
+ items: sectionInputs ? [] : (items ?? []),
119
261
  onNewChat,
120
262
  labels: railLabels,
121
263
  previewCount,
122
264
  expandedCount,
123
265
  });
266
+ const sectionControllers = useChatHistoryRailSectionsController(
267
+ sectionInputs ?? [],
268
+ railLabels.sections,
269
+ );
124
270
  const context: ChatHistoryRailRenderContext = {
125
271
  controller,
272
+ sections: (sectionInputs ?? []).map((section, index) => ({
273
+ section,
274
+ controller: sectionControllers[index],
275
+ })),
126
276
  listProps: { ...listProps, className, emptyLabel },
277
+ showNewChat,
127
278
  };
128
279
  const fallback = <DefaultChatHistoryRailView {...context} />;
129
280