@agent-native/toolkit 0.216.0 → 0.218.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 (261) hide show
  1. package/agent-native.eject.json +26 -0
  2. package/dist/app/agent-page/AgentContextTab.d.ts.map +1 -1
  3. package/dist/app/agent-page/AgentContextTab.js +1 -0
  4. package/dist/app/agent-page/AgentContextTab.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  6. package/dist/app/agentkit/react/components.js +1 -1
  7. package/dist/app/agentkit/react/components.js.map +1 -1
  8. package/dist/app/agentkit/react/context.d.ts +4 -1
  9. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  10. package/dist/app/agentkit/react/context.js +3 -1
  11. package/dist/app/agentkit/react/context.js.map +1 -1
  12. package/dist/app/agentkit/react/data-chart-layout.d.ts +30 -0
  13. package/dist/app/agentkit/react/data-chart-layout.d.ts.map +1 -0
  14. package/dist/app/agentkit/react/data-chart-layout.js +2 -0
  15. package/dist/app/agentkit/react/data-chart-layout.js.map +1 -0
  16. package/dist/app/agentkit/react/index.d.ts +1 -0
  17. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  18. package/dist/app/agentkit/react/index.js +1 -0
  19. package/dist/app/agentkit/react/index.js.map +1 -1
  20. package/dist/app/agentkit/react/root.d.ts +3 -1
  21. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  22. package/dist/app/agentkit/react/root.js +2 -2
  23. package/dist/app/agentkit/react/root.js.map +1 -1
  24. package/dist/app/agentkit/react/styles.css +18 -0
  25. package/dist/app/agentkit/react/transcript-layout.d.ts +2 -0
  26. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -1
  27. package/dist/app/agentkit/react/transcript-layout.js.map +1 -1
  28. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  29. package/dist/app/auth/AuthPage.js +33 -44
  30. package/dist/app/auth/AuthPage.js.map +1 -1
  31. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  32. package/dist/app/chat/AgentKitAssistantChat.js +2 -2
  33. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  34. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  35. package/dist/app/chat/MultiTabAssistantChat.js +51 -93
  36. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  37. package/dist/app/chat/agentkit-chat/action-widget.d.ts.map +1 -1
  38. package/dist/app/chat/agentkit-chat/action-widget.js +3 -1
  39. package/dist/app/chat/agentkit-chat/action-widget.js.map +1 -1
  40. package/dist/app/chat/agentkit-chat/rail.d.ts +1 -1
  41. package/dist/app/chat/agentkit-chat/rail.d.ts.map +1 -1
  42. package/dist/app/chat/agentkit-chat/rail.js +1 -1
  43. package/dist/app/chat/agentkit-chat/rail.js.map +1 -1
  44. package/dist/app/chat/chat/index.d.ts +2 -1
  45. package/dist/app/chat/chat/index.d.ts.map +1 -1
  46. package/dist/app/chat/chat/index.js +1 -1
  47. package/dist/app/chat/chat/index.js.map +1 -1
  48. package/dist/app/chat/chat/surface-types.d.ts +3 -0
  49. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  50. package/dist/app/chat/chat/surface-types.js.map +1 -1
  51. package/dist/app/chat/chat/tool-call-display.d.ts +3 -1
  52. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  53. package/dist/app/chat/chat/tool-call-display.js +4 -3
  54. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  55. package/dist/app/chat/chat/tool-render-registry.d.ts +2 -0
  56. package/dist/app/chat/chat/tool-render-registry.d.ts.map +1 -1
  57. package/dist/app/chat/chat/tool-render-registry.js.map +1 -1
  58. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts +3 -1
  59. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  60. package/dist/app/chat/chat/widgets/DataChartRenderer.js +116 -26
  61. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  62. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -1
  63. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  64. package/dist/app/chat/chat/widgets/DataChartWidget.js +21 -2
  65. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  66. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts +3 -1
  67. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts.map +1 -1
  68. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +2 -2
  69. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  70. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +1 -1
  71. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
  72. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +3 -1
  73. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -1
  74. package/dist/app/chat/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  75. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  76. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  77. package/dist/app/chat/chat-first/chat-history.d.ts +13 -2
  78. package/dist/app/chat/chat-first/chat-history.d.ts.map +1 -1
  79. package/dist/app/chat/chat-first/chat-history.js +24 -3
  80. package/dist/app/chat/chat-first/chat-history.js.map +1 -1
  81. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts +6 -0
  82. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts.map +1 -0
  83. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js +9 -0
  84. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js.map +1 -0
  85. package/dist/app/chat/ownership-history/index.d.ts +11 -0
  86. package/dist/app/chat/ownership-history/index.d.ts.map +1 -0
  87. package/dist/app/chat/ownership-history/index.js +22 -0
  88. package/dist/app/chat/ownership-history/index.js.map +1 -0
  89. package/dist/app/chat/ownership-history/rows.d.ts +27 -0
  90. package/dist/app/chat/ownership-history/rows.d.ts.map +1 -0
  91. package/dist/app/chat/ownership-history/rows.js +101 -0
  92. package/dist/app/chat/ownership-history/rows.js.map +1 -0
  93. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts +52 -0
  94. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts.map +1 -0
  95. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js +497 -0
  96. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js.map +1 -0
  97. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  98. package/dist/app/i18n/catalogs/ar-SA.js +3 -0
  99. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  100. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  101. package/dist/app/i18n/catalogs/de-DE.js +3 -0
  102. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  103. package/dist/app/i18n/catalogs/en-US.d.ts +3 -0
  104. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  105. package/dist/app/i18n/catalogs/en-US.js +3 -0
  106. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  107. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  108. package/dist/app/i18n/catalogs/es-ES.js +3 -0
  109. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  110. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  111. package/dist/app/i18n/catalogs/fr-FR.js +3 -0
  112. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  113. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  114. package/dist/app/i18n/catalogs/hi-IN.js +3 -0
  115. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  116. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  117. package/dist/app/i18n/catalogs/ja-JP.js +3 -0
  118. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  119. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  120. package/dist/app/i18n/catalogs/ko-KR.js +3 -0
  121. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  122. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  123. package/dist/app/i18n/catalogs/pt-BR.js +3 -0
  124. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  125. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  126. package/dist/app/i18n/catalogs/zh-CN.js +3 -0
  127. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  128. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  129. package/dist/app/i18n/catalogs/zh-TW.js +3 -0
  130. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  131. package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
  132. package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
  133. package/dist/app/onboarding/GettingStartedCard.js +38 -0
  134. package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
  135. package/dist/app/onboarding/index.d.ts +1 -0
  136. package/dist/app/onboarding/index.d.ts.map +1 -1
  137. package/dist/app/onboarding/index.js +1 -0
  138. package/dist/app/onboarding/index.js.map +1 -1
  139. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  140. package/dist/app/providers/AppProviders.js +3 -1
  141. package/dist/app/providers/AppProviders.js.map +1 -1
  142. package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
  143. package/dist/app/settings/AccountSettingsCard.js +2 -1
  144. package/dist/app/settings/AccountSettingsCard.js.map +1 -1
  145. package/dist/app/settings/index.d.ts +1 -0
  146. package/dist/app/settings/index.d.ts.map +1 -1
  147. package/dist/app/settings/index.js +1 -0
  148. package/dist/app/settings/index.js.map +1 -1
  149. package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
  150. package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
  151. package/dist/app/settings/shell/SettingsShell.js +87 -20
  152. package/dist/app/settings/shell/SettingsShell.js.map +1 -1
  153. package/dist/app/settings/shell/bridge.d.ts +5 -3
  154. package/dist/app/settings/shell/bridge.d.ts.map +1 -1
  155. package/dist/app/settings/shell/bridge.js +8 -5
  156. package/dist/app/settings/shell/bridge.js.map +1 -1
  157. package/dist/app/settings/shell/index.d.ts +2 -2
  158. package/dist/app/settings/shell/index.d.ts.map +1 -1
  159. package/dist/app/settings/shell/index.js.map +1 -1
  160. package/dist/app/settings/shell/registry.d.ts +9 -1
  161. package/dist/app/settings/shell/registry.d.ts.map +1 -1
  162. package/dist/app/settings/shell/registry.js +16 -0
  163. package/dist/app/settings/shell/registry.js.map +1 -1
  164. package/dist/app/settings/shell/routing.d.ts +5 -4
  165. package/dist/app/settings/shell/routing.d.ts.map +1 -1
  166. package/dist/app/settings/shell/routing.js +38 -10
  167. package/dist/app/settings/shell/routing.js.map +1 -1
  168. package/dist/app/shared/CommandMenu.d.ts +86 -6
  169. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  170. package/dist/app/shared/CommandMenu.js +303 -41
  171. package/dist/app/shared/CommandMenu.js.map +1 -1
  172. package/dist/app/shared/ConsentBanner.d.ts +9 -0
  173. package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
  174. package/dist/app/shared/ConsentBanner.js +35 -0
  175. package/dist/app/shared/ConsentBanner.js.map +1 -0
  176. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  177. package/dist/app/sharing/ShareButton.js +9 -8
  178. package/dist/app/sharing/ShareButton.js.map +1 -1
  179. package/dist/chat-history/ChatHistoryList.d.ts +16 -3
  180. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  181. package/dist/chat-history/ChatHistoryList.js +25 -15
  182. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  183. package/dist/chat-history.css +109 -9
  184. package/dist/ui/sonner.d.ts +2 -2
  185. package/dist/ui/sonner.d.ts.map +1 -1
  186. package/dist/ui/sonner.js +2 -2
  187. package/dist/ui/sonner.js.map +1 -1
  188. package/package.json +10 -3
  189. package/src/app/agent-page/AgentContextTab.spec.ts +50 -2
  190. package/src/app/agent-page/AgentContextTab.tsx +1 -0
  191. package/src/app/agentkit/react/components.interaction.spec.tsx +2 -0
  192. package/src/app/agentkit/react/components.spec.tsx +4 -1
  193. package/src/app/agentkit/react/components.tsx +3 -0
  194. package/src/app/agentkit/react/context.tsx +6 -0
  195. package/src/app/agentkit/react/data-chart-layout.ts +25 -0
  196. package/src/app/agentkit/react/index.ts +1 -0
  197. package/src/app/agentkit/react/root.tsx +4 -0
  198. package/src/app/agentkit/react/styles.css +18 -0
  199. package/src/app/agentkit/react/transcript-layout.ts +2 -0
  200. package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
  201. package/src/app/auth/AuthPage.tsx +32 -39
  202. package/src/app/chat/AgentKitAssistantChat.tsx +2 -0
  203. package/src/app/chat/MultiTabAssistantChat.spec.tsx +270 -3
  204. package/src/app/chat/MultiTabAssistantChat.tsx +79 -156
  205. package/src/app/chat/agentkit-chat/action-widget.tsx +3 -0
  206. package/src/app/chat/agentkit-chat/rail.ts +11 -0
  207. package/src/app/chat/chat/index.ts +10 -0
  208. package/src/app/chat/chat/surface-types.ts +3 -0
  209. package/src/app/chat/chat/tool-call-display.spec.tsx +29 -0
  210. package/src/app/chat/chat/tool-call-display.tsx +7 -0
  211. package/src/app/chat/chat/tool-render-registry.tsx +3 -0
  212. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +377 -17
  213. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +232 -63
  214. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +67 -0
  215. package/src/app/chat/chat/widgets/DataChartWidget.tsx +39 -5
  216. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +13 -2
  217. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +6 -4
  218. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -1
  219. package/src/app/chat/chat-first/chat-history.spec.tsx +79 -0
  220. package/src/app/chat/chat-first/chat-history.tsx +75 -22
  221. package/src/app/chat/ownership-history/ChatOwnerAvatar.tsx +27 -0
  222. package/src/app/chat/ownership-history/index.tsx +67 -0
  223. package/src/app/chat/ownership-history/ownership-history.spec.tsx +3146 -0
  224. package/src/app/chat/ownership-history/rows.ts +156 -0
  225. package/src/app/chat/ownership-history/useOwnershipChatHistory.ts +709 -0
  226. package/src/app/i18n/catalogs/ar-SA.ts +3 -0
  227. package/src/app/i18n/catalogs/de-DE.ts +3 -0
  228. package/src/app/i18n/catalogs/en-US.ts +3 -0
  229. package/src/app/i18n/catalogs/es-ES.ts +3 -0
  230. package/src/app/i18n/catalogs/fr-FR.ts +3 -0
  231. package/src/app/i18n/catalogs/hi-IN.ts +3 -0
  232. package/src/app/i18n/catalogs/ja-JP.ts +3 -0
  233. package/src/app/i18n/catalogs/ko-KR.ts +3 -0
  234. package/src/app/i18n/catalogs/pt-BR.ts +3 -0
  235. package/src/app/i18n/catalogs/zh-CN.ts +3 -0
  236. package/src/app/i18n/catalogs/zh-TW.ts +3 -0
  237. package/src/app/onboarding/GettingStartedCard.tsx +203 -0
  238. package/src/app/onboarding/index.ts +1 -0
  239. package/src/app/providers/AppProviders.tsx +7 -1
  240. package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
  241. package/src/app/settings/AccountSettingsCard.tsx +8 -0
  242. package/src/app/settings/index.ts +1 -0
  243. package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
  244. package/src/app/settings/shell/SettingsShell.tsx +245 -31
  245. package/src/app/settings/shell/bridge.spec.ts +12 -0
  246. package/src/app/settings/shell/bridge.ts +17 -4
  247. package/src/app/settings/shell/index.ts +6 -1
  248. package/src/app/settings/shell/registry.ts +33 -1
  249. package/src/app/settings/shell/routing.spec.ts +92 -1
  250. package/src/app/settings/shell/routing.ts +52 -9
  251. package/src/app/shared/CommandMenu.spec.tsx +1662 -1
  252. package/src/app/shared/CommandMenu.tsx +763 -164
  253. package/src/app/shared/ConsentBanner.spec.tsx +141 -0
  254. package/src/app/shared/ConsentBanner.tsx +141 -0
  255. package/src/app/sharing/ShareButton.tsx +12 -10
  256. package/src/chat-history/ChatHistoryList.spec.tsx +51 -0
  257. package/src/chat-history/ChatHistoryList.tsx +87 -26
  258. package/src/chat-history/ChatHistoryRailSections.spec.tsx +12 -4
  259. package/src/chat-history.css +109 -9
  260. package/src/ui/sonner.spec.ts +10 -0
  261. package/src/ui/sonner.tsx +2 -2
@@ -0,0 +1,141 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import { act } from "react";
4
+ import { createRoot, type Root } from "react-dom/client";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ const consentStateKey = Symbol.for("agent-native.client.consent");
8
+
9
+ let root: Root | null = null;
10
+ let container: HTMLElement | null = null;
11
+
12
+ async function renderBanner(region: "consent" | "none") {
13
+ vi.stubGlobal(
14
+ "fetch",
15
+ vi.fn(async () => new Response(JSON.stringify({ region }))),
16
+ );
17
+ vi.resetModules();
18
+ const consent = await import("@agent-native/core/client/consent");
19
+ const { ConsentBanner } = await import("./ConsentBanner.js");
20
+ container = document.createElement("div");
21
+ document.body.appendChild(container);
22
+ root = createRoot(container);
23
+ await act(async () => {
24
+ root!.render(<ConsentBanner />);
25
+ });
26
+ await act(async () => {
27
+ await consent.resolveConsentRegion();
28
+ });
29
+ return consent;
30
+ }
31
+
32
+ function banner() {
33
+ return document.querySelector("[data-agent-native-consent-banner]");
34
+ }
35
+
36
+ function button(name: string) {
37
+ const match = [...document.querySelectorAll("button")].find(
38
+ (el) => el.textContent === name,
39
+ );
40
+ if (!match) throw new Error(`no ${name} button`);
41
+ return match;
42
+ }
43
+
44
+ async function click(name: string) {
45
+ await act(async () => {
46
+ button(name).click();
47
+ });
48
+ }
49
+
50
+ describe("ConsentBanner", () => {
51
+ afterEach(() => {
52
+ act(() => root?.unmount());
53
+ container?.remove();
54
+ root = null;
55
+ delete (globalThis as any)[consentStateKey];
56
+ window.sessionStorage.clear();
57
+ for (const cookie of document.cookie.split(";")) {
58
+ const name = cookie.split("=")[0]?.trim();
59
+ if (name) document.cookie = `${name}=; path=/; max-age=0`;
60
+ }
61
+ vi.unstubAllGlobals();
62
+ });
63
+
64
+ it("shows nothing outside the consent region", async () => {
65
+ await renderBanner("none");
66
+ expect(banner()).toBeNull();
67
+ });
68
+
69
+ it("shows on a later page once the region is already known this session", async () => {
70
+ window.sessionStorage.setItem("an_consent_region", "consent");
71
+ const fetchMock = vi.fn(async () => new Response("{}"));
72
+ vi.stubGlobal("fetch", fetchMock);
73
+ vi.resetModules();
74
+ const { ConsentBanner } = await import("./ConsentBanner.js");
75
+ container = document.createElement("div");
76
+ document.body.appendChild(container);
77
+ root = createRoot(container);
78
+ await act(async () => {
79
+ root!.render(<ConsentBanner />);
80
+ });
81
+
82
+ expect(banner()).not.toBeNull();
83
+ expect(fetchMock).not.toHaveBeenCalled();
84
+ });
85
+
86
+ it("stores accept-all and closes", async () => {
87
+ const consent = await renderBanner("consent");
88
+ expect(banner()).not.toBeNull();
89
+
90
+ await click("Accept all");
91
+
92
+ expect(consent.getConsentChoice()).toEqual({
93
+ performance: true,
94
+ targeting: true,
95
+ });
96
+ expect(banner()).toBeNull();
97
+ });
98
+
99
+ it("stores reject-all and closes", async () => {
100
+ const consent = await renderBanner("consent");
101
+
102
+ await click("Reject all");
103
+
104
+ expect(consent.getConsentChoice()).toEqual({
105
+ performance: false,
106
+ targeting: false,
107
+ });
108
+ expect(banner()).toBeNull();
109
+ });
110
+
111
+ it("saves one category from the settings", async () => {
112
+ const consent = await renderBanner("consent");
113
+
114
+ await click("Manage settings");
115
+ await act(async () => {
116
+ (
117
+ document.getElementById("an-consent-performance") as HTMLElement
118
+ ).click();
119
+ });
120
+ await click("Save choices");
121
+
122
+ expect(consent.getConsentChoice()).toEqual({
123
+ performance: true,
124
+ targeting: false,
125
+ });
126
+ });
127
+
128
+ it("reopens from the settings link with the stored choice", async () => {
129
+ const consent = await renderBanner("consent");
130
+ await click("Accept all");
131
+
132
+ await act(async () => consent.openConsentSettings());
133
+ await click("Manage settings");
134
+
135
+ expect(
136
+ document
137
+ .getElementById("an-consent-targeting")
138
+ ?.getAttribute("aria-checked"),
139
+ ).toBe("true");
140
+ });
141
+ });
@@ -0,0 +1,141 @@
1
+ import {
2
+ getConsentChoice,
3
+ resolveConsentRegion,
4
+ setConsentChoice,
5
+ shouldShowConsentBanner,
6
+ subscribeConsent,
7
+ } from "@agent-native/core/client/consent";
8
+ import { useT } from "@agent-native/core/client/i18n";
9
+ import { Button } from "@agent-native/toolkit/ui/button";
10
+ import { Label } from "@agent-native/toolkit/ui/label";
11
+ import { Switch } from "@agent-native/toolkit/ui/switch";
12
+ import { useEffect, useState, useSyncExternalStore } from "react";
13
+
14
+ const DEFAULT_PRIVACY_POLICY_HREF = "https://www.agent-native.com/privacy";
15
+
16
+ /**
17
+ * The cookie banner for visitors in the consent region. Renders nothing
18
+ * elsewhere, and nothing once the visitor has chosen, until
19
+ * `openConsentSettings()` reopens it.
20
+ */
21
+ export function ConsentBanner({
22
+ privacyPolicyHref = DEFAULT_PRIVACY_POLICY_HREF,
23
+ }: {
24
+ privacyPolicyHref?: string;
25
+ }) {
26
+ const t = useT();
27
+ const open = useSyncExternalStore(
28
+ subscribeConsent,
29
+ shouldShowConsentBanner,
30
+ () => false,
31
+ );
32
+ const [managing, setManaging] = useState(false);
33
+ const [performance, setPerformance] = useState(false);
34
+ const [targeting, setTargeting] = useState(false);
35
+
36
+ useEffect(() => {
37
+ void resolveConsentRegion();
38
+ }, []);
39
+
40
+ useEffect(() => {
41
+ if (!open) return;
42
+ const stored = getConsentChoice();
43
+ setPerformance(stored?.performance ?? false);
44
+ setTargeting(stored?.targeting ?? false);
45
+ setManaging(false);
46
+ }, [open]);
47
+
48
+ if (!open) return null;
49
+
50
+ return (
51
+ <section
52
+ role="region"
53
+ aria-label={t("consentBanner.label")}
54
+ data-agent-native-consent-banner
55
+ className="fixed bottom-4 left-4 z-[100] flex w-[min(420px,calc(100vw-2rem))] flex-col gap-3 rounded-lg border bg-popover p-4 text-sm text-popover-foreground shadow-lg"
56
+ >
57
+ <p className="m-0 leading-relaxed">
58
+ {t("consentBanner.message")}{" "}
59
+ <a
60
+ href={privacyPolicyHref}
61
+ target="_blank"
62
+ rel="noreferrer"
63
+ className="underline underline-offset-2"
64
+ >
65
+ {t("consentBanner.privacyPolicy")}
66
+ </a>
67
+ </p>
68
+ {managing ? (
69
+ <div className="flex flex-col gap-2">
70
+ <div className="flex items-center justify-between gap-3">
71
+ <Label htmlFor="an-consent-necessary">
72
+ {t("consentBanner.necessary")}
73
+ </Label>
74
+ <Switch id="an-consent-necessary" checked disabled />
75
+ </div>
76
+ <div className="flex items-center justify-between gap-3">
77
+ <Label htmlFor="an-consent-performance">
78
+ {t("consentBanner.performance")}
79
+ </Label>
80
+ <Switch
81
+ id="an-consent-performance"
82
+ checked={performance}
83
+ onCheckedChange={setPerformance}
84
+ />
85
+ </div>
86
+ <div className="flex items-center justify-between gap-3">
87
+ <Label htmlFor="an-consent-targeting">
88
+ {t("consentBanner.targeting")}
89
+ </Label>
90
+ <Switch
91
+ id="an-consent-targeting"
92
+ checked={targeting}
93
+ onCheckedChange={setTargeting}
94
+ />
95
+ </div>
96
+ </div>
97
+ ) : null}
98
+ <div className="flex flex-wrap gap-2">
99
+ <Button
100
+ variant="outline"
101
+ size="sm"
102
+ className="flex-1"
103
+ onClick={() =>
104
+ setConsentChoice({ performance: false, targeting: false })
105
+ }
106
+ >
107
+ {t("consentBanner.rejectAll")}
108
+ </Button>
109
+ <Button
110
+ variant="outline"
111
+ size="sm"
112
+ className="flex-1"
113
+ onClick={() =>
114
+ setConsentChoice({ performance: true, targeting: true })
115
+ }
116
+ >
117
+ {t("consentBanner.acceptAll")}
118
+ </Button>
119
+ {managing ? (
120
+ <Button
121
+ variant="outline"
122
+ size="sm"
123
+ className="flex-1"
124
+ onClick={() => setConsentChoice({ performance, targeting })}
125
+ >
126
+ {t("consentBanner.save")}
127
+ </Button>
128
+ ) : (
129
+ <Button
130
+ variant="ghost"
131
+ size="sm"
132
+ className="flex-1"
133
+ onClick={() => setManaging(true)}
134
+ >
135
+ {t("consentBanner.manage")}
136
+ </Button>
137
+ )}
138
+ </div>
139
+ </section>
140
+ );
141
+ }
@@ -1150,6 +1150,12 @@ function MemberAutocomplete({
1150
1150
  activeIndex >= 0 && activeIndex < suggestions.length
1151
1151
  ? suggestions[activeIndex]
1152
1152
  : null;
1153
+ const listOpen =
1154
+ open &&
1155
+ (suggestions.length > 0 ||
1156
+ search.isLoading ||
1157
+ Boolean(search.error) ||
1158
+ value.trim().length > 0);
1153
1159
 
1154
1160
  useEffect(() => {
1155
1161
  setActiveIndex(-1);
@@ -1230,7 +1236,7 @@ function MemberAutocomplete({
1230
1236
  };
1231
1237
 
1232
1238
  return (
1233
- <Popover open={open} onOpenChange={onOpenChange}>
1239
+ <Popover open={listOpen} onOpenChange={onOpenChange}>
1234
1240
  <PopoverAnchor asChild>
1235
1241
  <div className="flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-input bg-card focus-within:ring-1 focus-within:ring-ring">
1236
1242
  <input
@@ -1238,8 +1244,8 @@ function MemberAutocomplete({
1238
1244
  type="text"
1239
1245
  role="combobox"
1240
1246
  aria-autocomplete="list"
1241
- aria-expanded={open}
1242
- aria-controls={open ? listboxId : undefined}
1247
+ aria-expanded={listOpen}
1248
+ aria-controls={listOpen ? listboxId : undefined}
1243
1249
  aria-activedescendant={
1244
1250
  activeIndex >= 0 ? optionId(listboxId, activeIndex) : undefined
1245
1251
  }
@@ -1361,13 +1367,9 @@ function MemberAutocomplete({
1361
1367
 
1362
1368
  {!search.isLoading && !search.error && suggestions.length === 0 ? (
1363
1369
  <div className="px-3 py-3 text-sm text-muted-foreground">
1364
- {value.trim()
1365
- ? t("agentChat.share.noMatches", {
1366
- defaultValue: "No matches.",
1367
- })
1368
- : t("agentChat.share.noPeopleFound", {
1369
- defaultValue: "No people found.",
1370
- })}
1370
+ {t("agentChat.share.noMatches", {
1371
+ defaultValue: "No matches.",
1372
+ })}
1371
1373
  </div>
1372
1374
  ) : null}
1373
1375
 
@@ -635,4 +635,55 @@ describe("ChatHistoryList", () => {
635
635
  });
636
636
  expect(container.querySelector(".an-chat-history-row__menu")).toBeNull();
637
637
  });
638
+
639
+ it("keeps a read-only row to its owner header and Copy link", async () => {
640
+ const onCopyLink = vi.fn();
641
+ const onDelete = vi.fn();
642
+ act(() => {
643
+ root.render(
644
+ <ChatHistoryList
645
+ items={[
646
+ item({
647
+ id: "shared",
648
+ readOnly: true,
649
+ ownerAvatar: <span data-testid="avatar">P</span>,
650
+ ownerLabel: "pat@example.com",
651
+ accessLabel: "Shared with your organization",
652
+ accessBasis: "organization",
653
+ }),
654
+ ]}
655
+ onSelect={() => {}}
656
+ onRename={() => {}}
657
+ onDelete={onDelete}
658
+ onTogglePin={() => {}}
659
+ onCopyLink={onCopyLink}
660
+ />,
661
+ );
662
+ });
663
+
664
+ const row = container.querySelector<HTMLElement>(".an-chat-history-row")!;
665
+ const mark = row.querySelector(".an-chat-history-row__owner-mark");
666
+ expect(mark?.getAttribute("data-access-basis")).toBe("organization");
667
+ expect(mark?.querySelector("[data-testid='avatar']")).not.toBeNull();
668
+ expect(
669
+ row.querySelector(".an-chat-history-row__topline")?.textContent,
670
+ ).toBe("sharedP");
671
+
672
+ await openMenu(
673
+ row.querySelector<HTMLButtonElement>(
674
+ ".an-chat-history-row__menu-trigger",
675
+ )!,
676
+ );
677
+ expect(
678
+ document.body.querySelector(".an-chat-history-row__menu-owner")
679
+ ?.textContent,
680
+ ).toBe("Ppat@example.comShared with your organization");
681
+ expect(getMenuItems().map((element) => element.textContent)).toEqual([
682
+ "Copy link",
683
+ ]);
684
+
685
+ act(() => getMenuItems()[0]?.click());
686
+ expect(onCopyLink).toHaveBeenCalledWith("shared");
687
+ expect(onDelete).not.toHaveBeenCalled();
688
+ });
638
689
  });
@@ -1,6 +1,7 @@
1
1
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
2
2
  import {
3
3
  IconDots,
4
+ IconLink,
4
5
  IconPencil,
5
6
  IconPinned,
6
7
  IconPinnedOff,
@@ -9,7 +10,7 @@ import {
9
10
  } from "@tabler/icons-react";
10
11
  import React, { useMemo, useRef, useState } from "react";
11
12
 
12
- import { Skeleton } from "../design-system/components.js";
13
+ import { Skeleton, Tooltip } from "../design-system/components.js";
13
14
  import { cn } from "../utils.js";
14
15
 
15
16
  /** How the viewer got access to a chat; the app supplies the translated copy. */
@@ -25,7 +26,12 @@ export interface ChatHistoryItem {
25
26
  titleText?: string;
26
27
  subtitle?: React.ReactNode;
27
28
  detail?: React.ReactNode;
28
- /** Already-translated owner name, shown on shared rows. */
29
+ /**
30
+ * A shared row's owner mark, kept on one line beside the timestamp. Hovering
31
+ * it shows `ownerLabel` and `accessLabel`, which also head the row menu.
32
+ */
33
+ ownerAvatar?: React.ReactNode;
34
+ /** Already-translated owner name for a shared row. */
29
35
  ownerLabel?: React.ReactNode;
30
36
  /** Already-translated access description, e.g. "Shared with you". */
31
37
  accessLabel?: React.ReactNode;
@@ -33,9 +39,14 @@ export interface ChatHistoryItem {
33
39
  timestamp?: React.ReactNode;
34
40
  pinned?: boolean;
35
41
  disabled?: boolean;
42
+ /**
43
+ * The viewer can open the chat but not rename, pin or delete it, e.g. a chat
44
+ * someone shared. The menu keeps the owner header and Copy link.
45
+ */
46
+ readOnly?: boolean;
36
47
  /**
37
48
  * 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.
49
+ * selectable, e.g. for a draft that has not been saved yet.
39
50
  */
40
51
  hideActions?: boolean;
41
52
  }
@@ -59,6 +70,7 @@ export interface ChatHistoryListLabels {
59
70
  rename: string;
60
71
  pin: string;
61
72
  unpin: string;
73
+ copyLink: string;
62
74
  delete: string;
63
75
  }
64
76
 
@@ -72,6 +84,8 @@ export interface ChatHistoryListProps {
72
84
  onRename?: (id: string, nextTitle: string) => void;
73
85
  renameMaxLength?: number;
74
86
  onDelete?: (id: string) => void;
87
+ /** Shown on every row, read-only ones included. */
88
+ onCopyLink?: (id: string) => void;
75
89
  renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
76
90
  renderAdditionalRowActions?: (
77
91
  item: ChatHistoryItem,
@@ -104,6 +118,7 @@ const DEFAULT_LABELS: ChatHistoryListLabels = {
104
118
  rename: "Rename",
105
119
  pin: "Pin to top",
106
120
  unpin: "Unpin from top",
121
+ copyLink: "Copy link",
107
122
  delete: "Delete",
108
123
  };
109
124
 
@@ -117,6 +132,7 @@ export function ChatHistoryList({
117
132
  onRename,
118
133
  renameMaxLength,
119
134
  onDelete,
135
+ onCopyLink,
120
136
  renderRowActions,
121
137
  renderAdditionalRowActions,
122
138
  searchValue,
@@ -141,9 +157,11 @@ export function ChatHistoryList({
141
157
  rename: labels?.rename ?? DEFAULT_LABELS.rename,
142
158
  pin: labels?.pin ?? DEFAULT_LABELS.pin,
143
159
  unpin: labels?.unpin ?? DEFAULT_LABELS.unpin,
160
+ copyLink: labels?.copyLink ?? DEFAULT_LABELS.copyLink,
144
161
  delete: labels?.delete ?? DEFAULT_LABELS.delete,
145
162
  }),
146
163
  [
164
+ labels?.copyLink,
147
165
  labels?.delete,
148
166
  labels?.options,
149
167
  labels?.pin,
@@ -252,6 +270,7 @@ export function ChatHistoryList({
252
270
  onRename={onRename}
253
271
  renameMaxLength={renameMaxLength}
254
272
  onDelete={onDelete}
273
+ onCopyLink={onCopyLink}
255
274
  renderRowActions={renderRowActions}
256
275
  renderAdditionalRowActions={renderAdditionalRowActions}
257
276
  labels={resolvedLabels}
@@ -289,6 +308,7 @@ type ChatHistoryRowProps = {
289
308
  onRename?: (id: string, nextTitle: string) => void;
290
309
  renameMaxLength?: number;
291
310
  onDelete?: (id: string) => void;
311
+ onCopyLink?: (id: string) => void;
292
312
  renderRowActions?: (item: ChatHistoryItem) => React.ReactNode;
293
313
  renderAdditionalRowActions?: (
294
314
  item: ChatHistoryItem,
@@ -297,6 +317,21 @@ type ChatHistoryRowProps = {
297
317
  labels: ChatHistoryListLabels;
298
318
  };
299
319
 
320
+ function ChatHistoryOwnerSummary({ item }: { item: ChatHistoryItem }) {
321
+ return (
322
+ <span className="an-chat-history-owner">
323
+ {item.ownerLabel != null && (
324
+ <span className="an-chat-history-owner__name">{item.ownerLabel}</span>
325
+ )}
326
+ {item.accessLabel != null && (
327
+ <span className="an-chat-history-owner__access">
328
+ {item.accessLabel}
329
+ </span>
330
+ )}
331
+ </span>
332
+ );
333
+ }
334
+
300
335
  const ChatHistoryRow = React.memo(function ChatHistoryRow({
301
336
  item,
302
337
  active,
@@ -306,6 +341,7 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
306
341
  onRename,
307
342
  renameMaxLength,
308
343
  onDelete,
344
+ onCopyLink,
309
345
  renderRowActions,
310
346
  renderAdditionalRowActions,
311
347
  labels,
@@ -315,12 +351,13 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
315
351
  const [menuOpen, setMenuOpen] = useState(false);
316
352
  const renameInputRef = useRef<HTMLInputElement | null>(null);
317
353
 
354
+ const editable = !item.readOnly;
355
+ const hasOwner = item.ownerLabel != null || item.accessLabel != null;
318
356
  const hasMenu =
319
357
  !item.hideActions &&
320
358
  Boolean(
321
- onTogglePin ||
322
- onRename ||
323
- onDelete ||
359
+ (editable && (onTogglePin || onRename || onDelete)) ||
360
+ onCopyLink ||
324
361
  renderRowActions ||
325
362
  renderAdditionalRowActions,
326
363
  );
@@ -402,6 +439,22 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
402
439
  >
403
440
  <div className="an-chat-history-row__topline">
404
441
  <span className="an-chat-history-row__title">{item.title}</span>
442
+ {item.ownerAvatar != null && (
443
+ <Tooltip
444
+ trigger={
445
+ <span
446
+ className="an-chat-history-row__owner-mark"
447
+ data-access-basis={item.accessBasis}
448
+ >
449
+ {item.ownerAvatar}
450
+ </span>
451
+ }
452
+ content={
453
+ hasOwner ? <ChatHistoryOwnerSummary item={item} /> : null
454
+ }
455
+ disabled={!hasOwner}
456
+ />
457
+ )}
405
458
  {item.timestamp != null && (
406
459
  <span className="an-chat-history-row__timestamp">
407
460
  {item.timestamp}
@@ -414,23 +467,6 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
414
467
  {item.detail != null && (
415
468
  <div className="an-chat-history-row__detail">{item.detail}</div>
416
469
  )}
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
- )}
434
470
  </button>
435
471
  )}
436
472
 
@@ -463,11 +499,22 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
463
499
  side="bottom"
464
500
  sideOffset={4}
465
501
  >
502
+ {hasOwner && (
503
+ <div
504
+ className="an-chat-history-row__menu-owner"
505
+ data-access-basis={item.accessBasis}
506
+ >
507
+ {item.ownerAvatar != null && (
508
+ <span aria-hidden="true">{item.ownerAvatar}</span>
509
+ )}
510
+ <ChatHistoryOwnerSummary item={item} />
511
+ </div>
512
+ )}
466
513
  {renderRowActions ? (
467
514
  renderRowActions(item)
468
515
  ) : (
469
516
  <>
470
- {onRename && (
517
+ {editable && onRename && (
471
518
  <button
472
519
  type="button"
473
520
  role="menuitem"
@@ -478,7 +525,7 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
478
525
  <span>{labels.rename}</span>
479
526
  </button>
480
527
  )}
481
- {onTogglePin && (
528
+ {editable && onTogglePin && (
482
529
  <button
483
530
  type="button"
484
531
  role="menuitem"
@@ -496,10 +543,24 @@ const ChatHistoryRow = React.memo(function ChatHistoryRow({
496
543
  <span>{item.pinned ? labels.unpin : labels.pin}</span>
497
544
  </button>
498
545
  )}
546
+ {onCopyLink && (
547
+ <button
548
+ type="button"
549
+ role="menuitem"
550
+ className="an-chat-history-row__menu-item"
551
+ onClick={() => {
552
+ setMenuOpen(false);
553
+ onCopyLink(item.id);
554
+ }}
555
+ >
556
+ <IconLink size={13} strokeWidth={1.8} />
557
+ <span>{labels.copyLink}</span>
558
+ </button>
559
+ )}
499
560
  {renderAdditionalRowActions?.(item, () =>
500
561
  setMenuOpen(false),
501
562
  )}
502
- {onDelete && (
563
+ {editable && onDelete && (
503
564
  <button
504
565
  type="button"
505
566
  role="menuitem"
@@ -273,7 +273,7 @@ describe("ChatHistoryRail sections", () => {
273
273
  expect(onLoadMore).toHaveBeenCalledOnce();
274
274
  });
275
275
 
276
- it("renders owner and access basis on a shared row", () => {
276
+ it("keeps a shared row on one line with the owner mark", () => {
277
277
  renderRail([
278
278
  {
279
279
  id: "shared",
@@ -281,16 +281,24 @@ describe("ChatHistoryRail sections", () => {
281
281
  {
282
282
  id: "s1",
283
283
  title: "Roadmap",
284
+ ownerAvatar: <span>P</span>,
284
285
  ownerLabel: "Pat Lee",
285
286
  accessLabel: "Shared with your organization",
286
287
  accessBasis: "organization",
288
+ readOnly: true,
287
289
  },
288
290
  ],
289
291
  },
290
292
  ]);
291
- const access = container.querySelector(".an-chat-history-row__access");
292
- expect(access?.getAttribute("data-access-basis")).toBe("organization");
293
- expect(access?.textContent).toBe("Pat LeeShared with your organization");
293
+ const row = container.querySelector(".an-chat-history-row")!;
294
+ expect(
295
+ row
296
+ .querySelector(".an-chat-history-row__owner-mark")
297
+ ?.getAttribute("data-access-basis"),
298
+ ).toBe("organization");
299
+ expect(row.querySelector(".an-chat-history-row__button")?.textContent).toBe(
300
+ "RoadmapP",
301
+ );
294
302
  });
295
303
 
296
304
  it("selecting a section row and New chat work as in the flat rail", () => {