@data-club/ai-hub 0.0.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 (344) hide show
  1. package/dist/auth/passwordPolicy.d.ts +55 -0
  2. package/dist/auth/passwordPolicy.d.ts.map +1 -0
  3. package/dist/auth/passwordPolicy.js +63 -0
  4. package/dist/auth/passwordPolicy.js.map +1 -0
  5. package/dist/auth/texts.d.ts +815 -0
  6. package/dist/auth/texts.d.ts.map +1 -0
  7. package/dist/auth/texts.js +429 -0
  8. package/dist/auth/texts.js.map +1 -0
  9. package/dist/client/AIHubClient.d.ts +1058 -0
  10. package/dist/client/AIHubClient.d.ts.map +1 -0
  11. package/dist/client/AIHubClient.js +950 -0
  12. package/dist/client/AIHubClient.js.map +1 -0
  13. package/dist/client/index.d.ts +11 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/client/index.js +9 -0
  16. package/dist/client/index.js.map +1 -0
  17. package/dist/client/sseParser.d.ts +41 -0
  18. package/dist/client/sseParser.d.ts.map +1 -0
  19. package/dist/client/sseParser.js +153 -0
  20. package/dist/client/sseParser.js.map +1 -0
  21. package/dist/client/tokenStorage.d.ts +56 -0
  22. package/dist/client/tokenStorage.d.ts.map +1 -0
  23. package/dist/client/tokenStorage.js +116 -0
  24. package/dist/client/tokenStorage.js.map +1 -0
  25. package/dist/components/AgentsPicker/index.d.ts +70 -0
  26. package/dist/components/AgentsPicker/index.d.ts.map +1 -0
  27. package/dist/components/AgentsPicker/index.js +63 -0
  28. package/dist/components/AgentsPicker/index.js.map +1 -0
  29. package/dist/components/AuthGate/index.d.ts +79 -0
  30. package/dist/components/AuthGate/index.d.ts.map +1 -0
  31. package/dist/components/AuthGate/index.js +61 -0
  32. package/dist/components/AuthGate/index.js.map +1 -0
  33. package/dist/components/CacheBadge/index.d.ts +88 -0
  34. package/dist/components/CacheBadge/index.d.ts.map +1 -0
  35. package/dist/components/CacheBadge/index.js +127 -0
  36. package/dist/components/CacheBadge/index.js.map +1 -0
  37. package/dist/components/ChangePassword/index.d.ts +109 -0
  38. package/dist/components/ChangePassword/index.d.ts.map +1 -0
  39. package/dist/components/ChangePassword/index.js +142 -0
  40. package/dist/components/ChangePassword/index.js.map +1 -0
  41. package/dist/components/ChatShell/ShellHeader.d.ts +44 -0
  42. package/dist/components/ChatShell/ShellHeader.d.ts.map +1 -0
  43. package/dist/components/ChatShell/ShellHeader.js +14 -0
  44. package/dist/components/ChatShell/ShellHeader.js.map +1 -0
  45. package/dist/components/ChatShell/UserMenu.d.ts +48 -0
  46. package/dist/components/ChatShell/UserMenu.d.ts.map +1 -0
  47. package/dist/components/ChatShell/UserMenu.js +86 -0
  48. package/dist/components/ChatShell/UserMenu.js.map +1 -0
  49. package/dist/components/ChatShell/index.d.ts +98 -0
  50. package/dist/components/ChatShell/index.d.ts.map +1 -0
  51. package/dist/components/ChatShell/index.js +124 -0
  52. package/dist/components/ChatShell/index.js.map +1 -0
  53. package/dist/components/DataClubAIHub/index.d.ts +132 -0
  54. package/dist/components/DataClubAIHub/index.d.ts.map +1 -0
  55. package/dist/components/DataClubAIHub/index.js +124 -0
  56. package/dist/components/DataClubAIHub/index.js.map +1 -0
  57. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts +89 -0
  58. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts.map +1 -0
  59. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js +199 -0
  60. package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js.map +1 -0
  61. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts +33 -0
  62. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts.map +1 -0
  63. package/dist/components/DataClubAIHubAdmin/AgentsPage.js +137 -0
  64. package/dist/components/DataClubAIHubAdmin/AgentsPage.js.map +1 -0
  65. package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts +50 -0
  66. package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts.map +1 -0
  67. package/dist/components/DataClubAIHubAdmin/CreateUserModal.js +93 -0
  68. package/dist/components/DataClubAIHubAdmin/CreateUserModal.js.map +1 -0
  69. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts +49 -0
  70. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts.map +1 -0
  71. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js +94 -0
  72. package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js.map +1 -0
  73. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts +37 -0
  74. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts.map +1 -0
  75. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js +44 -0
  76. package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js.map +1 -0
  77. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts +56 -0
  78. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts.map +1 -0
  79. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js +158 -0
  80. package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js.map +1 -0
  81. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts +29 -0
  82. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts.map +1 -0
  83. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js +183 -0
  84. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js.map +1 -0
  85. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts +38 -0
  86. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts.map +1 -0
  87. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js +294 -0
  88. package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js.map +1 -0
  89. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts +36 -0
  90. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -0
  91. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +373 -0
  92. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -0
  93. package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts +47 -0
  94. package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts.map +1 -0
  95. package/dist/components/DataClubAIHubAdmin/UsersPage.js +233 -0
  96. package/dist/components/DataClubAIHubAdmin/UsersPage.js.map +1 -0
  97. package/dist/components/DataClubAIHubAdmin/index.d.ts +87 -0
  98. package/dist/components/DataClubAIHubAdmin/index.d.ts.map +1 -0
  99. package/dist/components/DataClubAIHubAdmin/index.js +137 -0
  100. package/dist/components/DataClubAIHubAdmin/index.js.map +1 -0
  101. package/dist/components/DataClubAIHubLogin/index.d.ts +113 -0
  102. package/dist/components/DataClubAIHubLogin/index.d.ts.map +1 -0
  103. package/dist/components/DataClubAIHubLogin/index.js +123 -0
  104. package/dist/components/DataClubAIHubLogin/index.js.map +1 -0
  105. package/dist/components/EffortControl/index.d.ts +57 -0
  106. package/dist/components/EffortControl/index.d.ts.map +1 -0
  107. package/dist/components/EffortControl/index.js +84 -0
  108. package/dist/components/EffortControl/index.js.map +1 -0
  109. package/dist/components/Layout/index.d.ts +63 -0
  110. package/dist/components/Layout/index.d.ts.map +1 -0
  111. package/dist/components/Layout/index.js +45 -0
  112. package/dist/components/Layout/index.js.map +1 -0
  113. package/dist/components/MainColumn/index.d.ts +69 -0
  114. package/dist/components/MainColumn/index.d.ts.map +1 -0
  115. package/dist/components/MainColumn/index.js +47 -0
  116. package/dist/components/MainColumn/index.js.map +1 -0
  117. package/dist/components/MarkdownRenderer/index.d.ts +66 -0
  118. package/dist/components/MarkdownRenderer/index.d.ts.map +1 -0
  119. package/dist/components/MarkdownRenderer/index.js +153 -0
  120. package/dist/components/MarkdownRenderer/index.js.map +1 -0
  121. package/dist/components/MessageInput/index.d.ts +85 -0
  122. package/dist/components/MessageInput/index.d.ts.map +1 -0
  123. package/dist/components/MessageInput/index.js +139 -0
  124. package/dist/components/MessageInput/index.js.map +1 -0
  125. package/dist/components/MessageList/index.d.ts +156 -0
  126. package/dist/components/MessageList/index.d.ts.map +1 -0
  127. package/dist/components/MessageList/index.js +352 -0
  128. package/dist/components/MessageList/index.js.map +1 -0
  129. package/dist/components/ReasoningSelector/index.d.ts +77 -0
  130. package/dist/components/ReasoningSelector/index.d.ts.map +1 -0
  131. package/dist/components/ReasoningSelector/index.js +156 -0
  132. package/dist/components/ReasoningSelector/index.js.map +1 -0
  133. package/dist/components/ReasoningView/index.d.ts +94 -0
  134. package/dist/components/ReasoningView/index.d.ts.map +1 -0
  135. package/dist/components/ReasoningView/index.js +83 -0
  136. package/dist/components/ReasoningView/index.js.map +1 -0
  137. package/dist/components/ScrollToPresentButton/index.d.ts +54 -0
  138. package/dist/components/ScrollToPresentButton/index.d.ts.map +1 -0
  139. package/dist/components/ScrollToPresentButton/index.js +44 -0
  140. package/dist/components/ScrollToPresentButton/index.js.map +1 -0
  141. package/dist/components/SessionsPanel/SessionTitleModal.d.ts +79 -0
  142. package/dist/components/SessionsPanel/SessionTitleModal.d.ts.map +1 -0
  143. package/dist/components/SessionsPanel/SessionTitleModal.js +102 -0
  144. package/dist/components/SessionsPanel/SessionTitleModal.js.map +1 -0
  145. package/dist/components/SessionsPanel/index.d.ts +100 -0
  146. package/dist/components/SessionsPanel/index.d.ts.map +1 -0
  147. package/dist/components/SessionsPanel/index.js +176 -0
  148. package/dist/components/SessionsPanel/index.js.map +1 -0
  149. package/dist/components/SystemNotice/index.d.ts +66 -0
  150. package/dist/components/SystemNotice/index.d.ts.map +1 -0
  151. package/dist/components/SystemNotice/index.js +81 -0
  152. package/dist/components/SystemNotice/index.js.map +1 -0
  153. package/dist/components/ThemeSwitcher/index.d.ts +66 -0
  154. package/dist/components/ThemeSwitcher/index.d.ts.map +1 -0
  155. package/dist/components/ThemeSwitcher/index.js +73 -0
  156. package/dist/components/ThemeSwitcher/index.js.map +1 -0
  157. package/dist/components/ToolCallView/formatContent.d.ts +36 -0
  158. package/dist/components/ToolCallView/formatContent.d.ts.map +1 -0
  159. package/dist/components/ToolCallView/formatContent.js +53 -0
  160. package/dist/components/ToolCallView/formatContent.js.map +1 -0
  161. package/dist/components/ToolCallView/index.d.ts +120 -0
  162. package/dist/components/ToolCallView/index.d.ts.map +1 -0
  163. package/dist/components/ToolCallView/index.js +104 -0
  164. package/dist/components/ToolCallView/index.js.map +1 -0
  165. package/dist/components/TpmBanner/index.d.ts +53 -0
  166. package/dist/components/TpmBanner/index.d.ts.map +1 -0
  167. package/dist/components/TpmBanner/index.js +116 -0
  168. package/dist/components/TpmBanner/index.js.map +1 -0
  169. package/dist/components/TurnStatusIndicator/index.d.ts +77 -0
  170. package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -0
  171. package/dist/components/TurnStatusIndicator/index.js +122 -0
  172. package/dist/components/TurnStatusIndicator/index.js.map +1 -0
  173. package/dist/components/internal/PasswordInput.d.ts +62 -0
  174. package/dist/components/internal/PasswordInput.d.ts.map +1 -0
  175. package/dist/components/internal/PasswordInput.js +42 -0
  176. package/dist/components/internal/PasswordInput.js.map +1 -0
  177. package/dist/context/AIHubContext.d.ts +220 -0
  178. package/dist/context/AIHubContext.d.ts.map +1 -0
  179. package/dist/context/AIHubContext.js +19 -0
  180. package/dist/context/AIHubContext.js.map +1 -0
  181. package/dist/context/AIHubProvider.d.ts +75 -0
  182. package/dist/context/AIHubProvider.d.ts.map +1 -0
  183. package/dist/context/AIHubProvider.js +954 -0
  184. package/dist/context/AIHubProvider.js.map +1 -0
  185. package/dist/context/index.d.ts +9 -0
  186. package/dist/context/index.d.ts.map +1 -0
  187. package/dist/context/index.js +9 -0
  188. package/dist/context/index.js.map +1 -0
  189. package/dist/context/theme.d.ts +43 -0
  190. package/dist/context/theme.d.ts.map +1 -0
  191. package/dist/context/theme.js +70 -0
  192. package/dist/context/theme.js.map +1 -0
  193. package/dist/context/useAIHub.d.ts +104 -0
  194. package/dist/context/useAIHub.d.ts.map +1 -0
  195. package/dist/context/useAIHub.js +63 -0
  196. package/dist/context/useAIHub.js.map +1 -0
  197. package/dist/context/useAIHubAuth.d.ts +44 -0
  198. package/dist/context/useAIHubAuth.d.ts.map +1 -0
  199. package/dist/context/useAIHubAuth.js +35 -0
  200. package/dist/context/useAIHubAuth.js.map +1 -0
  201. package/dist/index.d.ts +76 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +57 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/primitives/index.d.ts +99 -0
  206. package/dist/primitives/index.d.ts.map +1 -0
  207. package/dist/primitives/index.js +97 -0
  208. package/dist/primitives/index.js.map +1 -0
  209. package/dist/state/affordances.d.ts +182 -0
  210. package/dist/state/affordances.d.ts.map +1 -0
  211. package/dist/state/affordances.js +189 -0
  212. package/dist/state/affordances.js.map +1 -0
  213. package/dist/state/stateReducer.d.ts +72 -0
  214. package/dist/state/stateReducer.d.ts.map +1 -0
  215. package/dist/state/stateReducer.js +122 -0
  216. package/dist/state/stateReducer.js.map +1 -0
  217. package/dist/stories/EffortControl.stories.d.ts +14 -0
  218. package/dist/stories/EffortControl.stories.d.ts.map +1 -0
  219. package/dist/stories/EffortControl.stories.js +43 -0
  220. package/dist/stories/EffortControl.stories.js.map +1 -0
  221. package/dist/stories/MarkdownRenderer.stories.d.ts +25 -0
  222. package/dist/stories/MarkdownRenderer.stories.d.ts.map +1 -0
  223. package/dist/stories/MarkdownRenderer.stories.js +178 -0
  224. package/dist/stories/MarkdownRenderer.stories.js.map +1 -0
  225. package/dist/stories/MessageBubble.stories.d.ts +21 -0
  226. package/dist/stories/MessageBubble.stories.d.ts.map +1 -0
  227. package/dist/stories/MessageBubble.stories.js +59 -0
  228. package/dist/stories/MessageBubble.stories.js.map +1 -0
  229. package/dist/stories/ReasoningView.stories.d.ts +18 -0
  230. package/dist/stories/ReasoningView.stories.d.ts.map +1 -0
  231. package/dist/stories/ReasoningView.stories.js +62 -0
  232. package/dist/stories/ReasoningView.stories.js.map +1 -0
  233. package/dist/stories/ScrollToPresentButton.stories.d.ts +18 -0
  234. package/dist/stories/ScrollToPresentButton.stories.d.ts.map +1 -0
  235. package/dist/stories/ScrollToPresentButton.stories.js +34 -0
  236. package/dist/stories/ScrollToPresentButton.stories.js.map +1 -0
  237. package/dist/stories/SystemNotice.stories.d.ts +17 -0
  238. package/dist/stories/SystemNotice.stories.d.ts.map +1 -0
  239. package/dist/stories/SystemNotice.stories.js +42 -0
  240. package/dist/stories/SystemNotice.stories.js.map +1 -0
  241. package/dist/stories/ThemeSwitcher.stories.d.ts +15 -0
  242. package/dist/stories/ThemeSwitcher.stories.d.ts.map +1 -0
  243. package/dist/stories/ThemeSwitcher.stories.js +35 -0
  244. package/dist/stories/ThemeSwitcher.stories.js.map +1 -0
  245. package/dist/stories/ToolCallView.stories.d.ts +20 -0
  246. package/dist/stories/ToolCallView.stories.d.ts.map +1 -0
  247. package/dist/stories/ToolCallView.stories.js +65 -0
  248. package/dist/stories/ToolCallView.stories.js.map +1 -0
  249. package/dist/stories/TpmBanner.stories.d.ts +24 -0
  250. package/dist/stories/TpmBanner.stories.d.ts.map +1 -0
  251. package/dist/stories/TpmBanner.stories.js +63 -0
  252. package/dist/stories/TpmBanner.stories.js.map +1 -0
  253. package/dist/stories/TurnStatusIndicator.stories.d.ts +25 -0
  254. package/dist/stories/TurnStatusIndicator.stories.d.ts.map +1 -0
  255. package/dist/stories/TurnStatusIndicator.stories.js +56 -0
  256. package/dist/stories/TurnStatusIndicator.stories.js.map +1 -0
  257. package/dist/stories/_mockProvider.d.ts +44 -0
  258. package/dist/stories/_mockProvider.d.ts.map +1 -0
  259. package/dist/stories/_mockProvider.js +84 -0
  260. package/dist/stories/_mockProvider.js.map +1 -0
  261. package/dist/types.d.ts +787 -0
  262. package/dist/types.d.ts.map +1 -0
  263. package/dist/types.js +19 -0
  264. package/dist/types.js.map +1 -0
  265. package/dist/webcomponent/defineWebComponent.d.ts +106 -0
  266. package/dist/webcomponent/defineWebComponent.d.ts.map +1 -0
  267. package/dist/webcomponent/defineWebComponent.js +126 -0
  268. package/dist/webcomponent/defineWebComponent.js.map +1 -0
  269. package/dist/webcomponent/index.d.ts +7 -0
  270. package/dist/webcomponent/index.d.ts.map +1 -0
  271. package/dist/webcomponent/index.js +7 -0
  272. package/dist/webcomponent/index.js.map +1 -0
  273. package/package.json +63 -0
  274. package/src/__lint-canary__/step1-lint-canary.ts.disabled +28 -0
  275. package/src/auth/passwordPolicy.ts +75 -0
  276. package/src/auth/texts.ts +1294 -0
  277. package/src/client/AIHubClient.ts +1718 -0
  278. package/src/client/index.ts +30 -0
  279. package/src/client/sseParser.ts +168 -0
  280. package/src/client/tokenStorage.ts +131 -0
  281. package/src/components/AgentsPicker/index.tsx +147 -0
  282. package/src/components/AuthGate/index.tsx +132 -0
  283. package/src/components/CacheBadge/index.tsx +245 -0
  284. package/src/components/ChangePassword/index.tsx +375 -0
  285. package/src/components/ChatShell/ShellHeader.tsx +80 -0
  286. package/src/components/ChatShell/UserMenu.tsx +196 -0
  287. package/src/components/ChatShell/index.tsx +307 -0
  288. package/src/components/DataClubAIHub/index.tsx +300 -0
  289. package/src/components/DataClubAIHubAdmin/AgentEditorModal.tsx +505 -0
  290. package/src/components/DataClubAIHubAdmin/AgentsPage.tsx +342 -0
  291. package/src/components/DataClubAIHubAdmin/CreateUserModal.tsx +214 -0
  292. package/src/components/DataClubAIHubAdmin/PasswordRevealModal.tsx +204 -0
  293. package/src/components/DataClubAIHubAdmin/ResetPasswordModal.tsx +122 -0
  294. package/src/components/DataClubAIHubAdmin/RotatingMessageEditorModal.tsx +408 -0
  295. package/src/components/DataClubAIHubAdmin/RotatingMessagesPage.tsx +373 -0
  296. package/src/components/DataClubAIHubAdmin/SessionsOverviewPage.tsx +639 -0
  297. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +979 -0
  298. package/src/components/DataClubAIHubAdmin/UsersPage.tsx +495 -0
  299. package/src/components/DataClubAIHubAdmin/index.tsx +277 -0
  300. package/src/components/DataClubAIHubLogin/index.tsx +282 -0
  301. package/src/components/EffortControl/index.tsx +138 -0
  302. package/src/components/Layout/index.tsx +92 -0
  303. package/src/components/MainColumn/index.tsx +131 -0
  304. package/src/components/MarkdownRenderer/index.tsx +256 -0
  305. package/src/components/MessageInput/index.tsx +283 -0
  306. package/src/components/MessageList/index.tsx +753 -0
  307. package/src/components/ReasoningSelector/index.tsx +268 -0
  308. package/src/components/ReasoningView/index.tsx +183 -0
  309. package/src/components/ScrollToPresentButton/index.tsx +85 -0
  310. package/src/components/SessionsPanel/SessionTitleModal.tsx +256 -0
  311. package/src/components/SessionsPanel/index.tsx +406 -0
  312. package/src/components/SystemNotice/index.tsx +151 -0
  313. package/src/components/ThemeSwitcher/index.tsx +136 -0
  314. package/src/components/ToolCallView/formatContent.ts +50 -0
  315. package/src/components/ToolCallView/index.tsx +274 -0
  316. package/src/components/TpmBanner/index.tsx +172 -0
  317. package/src/components/TurnStatusIndicator/index.tsx +203 -0
  318. package/src/components/internal/PasswordInput.tsx +145 -0
  319. package/src/context/AIHubContext.ts +249 -0
  320. package/src/context/AIHubProvider.tsx +1084 -0
  321. package/src/context/index.ts +9 -0
  322. package/src/context/theme.ts +76 -0
  323. package/src/context/useAIHub.ts +151 -0
  324. package/src/context/useAIHubAuth.ts +65 -0
  325. package/src/index.ts +275 -0
  326. package/src/primitives/index.ts +101 -0
  327. package/src/state/affordances.ts +321 -0
  328. package/src/state/stateReducer.ts +131 -0
  329. package/src/stories/EffortControl.stories.tsx +63 -0
  330. package/src/stories/MarkdownRenderer.stories.tsx +201 -0
  331. package/src/stories/MessageBubble.stories.tsx +101 -0
  332. package/src/stories/ReasoningView.stories.tsx +81 -0
  333. package/src/stories/ScrollToPresentButton.stories.tsx +52 -0
  334. package/src/stories/SystemNotice.stories.tsx +59 -0
  335. package/src/stories/ThemeSwitcher.stories.tsx +55 -0
  336. package/src/stories/ToolCallView.stories.tsx +95 -0
  337. package/src/stories/TpmBanner.stories.tsx +93 -0
  338. package/src/stories/TurnStatusIndicator.stories.tsx +86 -0
  339. package/src/stories/_mockProvider.tsx +116 -0
  340. package/src/styles/CLASSES.md +356 -0
  341. package/src/styles/default.css +2937 -0
  342. package/src/types.ts +867 -0
  343. package/src/webcomponent/defineWebComponent.tsx +193 -0
  344. package/src/webcomponent/index.ts +11 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * `<CacheBadge />` — small per-message badge surfacing provider token
3
+ * usage on an assistant bubble. Renders an inline icon + a compact
4
+ * token count, with the full breakdown in a `title`-attribute tooltip
5
+ * on hover.
6
+ *
7
+ * **What it shows.** Letta reports five things per assistant turn (via
8
+ * `AIHubMessageUsage`): input tokens, output tokens, cache-read tokens
9
+ * (prompt-cache HIT — provider charged ~0.1x), cache-write tokens
10
+ * (provider charged ~1.25x, Anthropic only), reasoning tokens, and a
11
+ * rolling context-window estimate.
12
+ *
13
+ * The headline number we show in the badge is the **cache-hit
14
+ * percentage of the prompt** — `cache_read / (input + cache_read +
15
+ * cache_write)`. That number is the single most useful at-a-glance
16
+ * signal:
17
+ * - 0% → cache is cold (cost is the worst case)
18
+ * - 80% → ~10x cheaper system prompt than a cold turn
19
+ *
20
+ * The full numbers live in the tooltip. We use the native `title`
21
+ * attribute instead of a custom popover to keep the component
22
+ * lightweight and dependency-free (no portals, no animation, no
23
+ * focus management — perfect for a peripheral info chip).
24
+ *
25
+ * **When to render.** Only when the parent passes a `usage` prop with
26
+ * at least one non-null number. Messages without a usage stamp (e.g.
27
+ * historical conversations from before this feature shipped, or
28
+ * tool-only assistant turns where Letta didn't report anything)
29
+ * simply don't render the badge.
30
+ *
31
+ * Exported under `AIHub.CacheBadge` (re-exported via the namespace).
32
+ *
33
+ * @see AIHubMessageUsage in `../../types.ts`
34
+ * @see SPECIFICATIONS.md §9.4 — `usage` SSE event
35
+ */
36
+
37
+ import type { ReactNode } from "react";
38
+
39
+ import type { AIHubMessageUsage } from "../../types.js";
40
+
41
+ /** BEM class names emitted by `<CacheBadge />`. */
42
+ export interface CacheBadgeClassNames {
43
+ /** Outer span; `data-cache-state="hot|warm|cold|none"` for skinning. */
44
+ root: string;
45
+ /** The leading icon character (default 💾). */
46
+ icon: string;
47
+ /** The headline percentage label. */
48
+ label: string;
49
+ }
50
+
51
+ const DEFAULT_CLASS_NAMES: CacheBadgeClassNames = Object.freeze({
52
+ root: "data-club-ai-hub__cache-badge",
53
+ icon: "data-club-ai-hub__cache-badge__icon",
54
+ label: "data-club-ai-hub__cache-badge__label",
55
+ }) as CacheBadgeClassNames;
56
+
57
+ /** Localizable strings for the badge headline + tooltip. */
58
+ export interface CacheBadgeTexts {
59
+ /** Headline shown when there's no cache info at all (e.g. providers that don't report). */
60
+ noCacheLabel: string;
61
+ /** Headline shown when this turn was a full cache miss (no tokens served from cache). */
62
+ missLabel: string;
63
+ /** Headline shown when this turn was a cache priming write — read=0 but write>0. */
64
+ writePrimingLabel: string;
65
+ /** Headline prefix for cache hits — full text is `${hitLabel} ${N}%`. */
66
+ hitLabel: string;
67
+ /** Tooltip line prefixes. Keep short — the tooltip is `title` only. */
68
+ hitTooltipLabel: string;
69
+ writeTooltipLabel: string;
70
+ inputLabel: string;
71
+ outputLabel: string;
72
+ reasoningLabel: string;
73
+ contextLabel: string;
74
+ totalLabel: string;
75
+ }
76
+
77
+ export const DEFAULT_TEXTS: CacheBadgeTexts = Object.freeze({
78
+ noCacheLabel: "no data",
79
+ missLabel: "MISS",
80
+ writePrimingLabel: "PRIMING",
81
+ hitLabel: "HIT",
82
+ hitTooltipLabel: "Cache hit",
83
+ writeTooltipLabel: "Cache write",
84
+ inputLabel: "Input",
85
+ outputLabel: "Output",
86
+ reasoningLabel: "Reasoning",
87
+ contextLabel: "Context",
88
+ totalLabel: "Total",
89
+ }) as CacheBadgeTexts;
90
+
91
+ /** Props for `<CacheBadge />`. */
92
+ export interface CacheBadgeProps {
93
+ /** Per-message usage, as returned by `GET /sessions/:id/messages`. */
94
+ usage: AIHubMessageUsage;
95
+ /** Optional class-name overrides. */
96
+ classNames?: Partial<CacheBadgeClassNames>;
97
+ /** Optional text overrides (l10n). */
98
+ texts?: Partial<CacheBadgeTexts>;
99
+ }
100
+
101
+ /**
102
+ * Computes the cache-hit ratio (0..1) over the prompt-side tokens.
103
+ *
104
+ * Denominator: `input + cache_read + cache_write` — every prompt
105
+ * token the provider saw, regardless of whether it served from cache
106
+ * or wrote new. We exclude `output` and `reasoning` because those
107
+ * are completion-side and can't benefit from prompt caching.
108
+ *
109
+ * Exported so the test suite can assert numerically.
110
+ */
111
+ export function computeCacheHitRatio(usage: AIHubMessageUsage): number | null {
112
+ const read = usage.cache_read ?? 0;
113
+ const write = usage.cache_write ?? 0;
114
+ const input = usage.input ?? 0;
115
+ const denom = read + write + input;
116
+ if (denom === 0) return null;
117
+ return read / denom;
118
+ }
119
+
120
+ /**
121
+ * Bucket the usage into a discrete state for skinning + label choice.
122
+ *
123
+ * - `none` — no prompt-side tokens reported at all (provider
124
+ * didn't expose usage, or empty turn). Badge shows
125
+ * the "no data" label.
126
+ * - `miss` — full cache miss: `cache_read = 0` AND
127
+ * `cache_write = 0` (or not reported). The provider
128
+ * had to reinitialize the whole prompt. Badge shows
129
+ * "MISS" in cold (grey) styling.
130
+ * - `priming` — `cache_read = 0` but `cache_write > 0`. This
131
+ * turn's prompt was a CACHE WRITE — the provider
132
+ * paid full price now and future turns benefit.
133
+ * Anthropic only (the other providers don't report
134
+ * writes). Badge shows "PRIMING".
135
+ * - `cold` — `cache_read > 0` but ratio < 10%. Hit registered
136
+ * but most of the prompt was new.
137
+ * - `warm` — 10–60% of the prompt was cached.
138
+ * - `hot` — 60%+ of the prompt was cached.
139
+ */
140
+ type CacheState = "none" | "miss" | "priming" | "cold" | "warm" | "hot";
141
+
142
+ function bucketState(usage: AIHubMessageUsage, ratio: number | null): CacheState {
143
+ if (ratio === null) return "none";
144
+ if (ratio === 0) {
145
+ // No cache hit. Distinguish full-miss (nothing cached anywhere)
146
+ // from priming (we WROTE to the cache this turn — next turn will
147
+ // benefit). The latter is good news, the former is bad news, so
148
+ // the user-facing labels should differ.
149
+ return (usage.cache_write ?? 0) > 0 ? "priming" : "miss";
150
+ }
151
+ if (ratio >= 0.6) return "hot";
152
+ if (ratio >= 0.1) return "warm";
153
+ return "cold";
154
+ }
155
+
156
+ /** Formats a token count as a compact human-readable string: 1234 → 1.2k. */
157
+ function formatTokens(n: number | null | undefined): string {
158
+ if (n === null || n === undefined) return "—";
159
+ if (n < 1000) return String(n);
160
+ if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
161
+ return `${(n / 1_000_000).toFixed(2)}M`;
162
+ }
163
+
164
+ /** Builds the `title`-attribute tooltip text from the usage object. */
165
+ function buildTooltip(usage: AIHubMessageUsage, texts: CacheBadgeTexts): string {
166
+ const lines: string[] = [];
167
+ if (usage.cache_read !== null && usage.cache_read !== undefined) {
168
+ lines.push(`${texts.hitTooltipLabel}: ${formatTokens(usage.cache_read)}`);
169
+ }
170
+ if (usage.cache_write !== null && usage.cache_write !== undefined) {
171
+ lines.push(`${texts.writeTooltipLabel}: ${formatTokens(usage.cache_write)}`);
172
+ }
173
+ if (usage.input !== null && usage.input !== undefined) {
174
+ lines.push(`${texts.inputLabel}: ${formatTokens(usage.input)}`);
175
+ }
176
+ if (usage.output !== null && usage.output !== undefined) {
177
+ lines.push(`${texts.outputLabel}: ${formatTokens(usage.output)}`);
178
+ }
179
+ if (usage.reasoning !== null && usage.reasoning !== undefined && usage.reasoning > 0) {
180
+ lines.push(`${texts.reasoningLabel}: ${formatTokens(usage.reasoning)}`);
181
+ }
182
+ if (usage.context !== null && usage.context !== undefined) {
183
+ lines.push(`${texts.contextLabel}: ${formatTokens(usage.context)}`);
184
+ }
185
+ if (usage.total !== null && usage.total !== undefined) {
186
+ lines.push(`${texts.totalLabel}: ${formatTokens(usage.total)}`);
187
+ }
188
+ return lines.join("\n");
189
+ }
190
+
191
+ /** Returns true when the usage object has at least one usable numeric field. */
192
+ function hasAnyData(usage: AIHubMessageUsage): boolean {
193
+ return (
194
+ (usage.input ?? null) !== null ||
195
+ (usage.output ?? null) !== null ||
196
+ (usage.cache_read ?? null) !== null ||
197
+ (usage.cache_write ?? null) !== null ||
198
+ (usage.reasoning ?? null) !== null ||
199
+ (usage.context ?? null) !== null ||
200
+ (usage.total ?? null) !== null
201
+ );
202
+ }
203
+
204
+ export function CacheBadge({
205
+ usage,
206
+ classNames,
207
+ texts,
208
+ }: CacheBadgeProps): ReactNode {
209
+ if (!hasAnyData(usage)) return null;
210
+ const cls: CacheBadgeClassNames = { ...DEFAULT_CLASS_NAMES, ...(classNames ?? {}) };
211
+ const t: CacheBadgeTexts = { ...DEFAULT_TEXTS, ...(texts ?? {}) };
212
+
213
+ const ratio = computeCacheHitRatio(usage);
214
+ const state = bucketState(usage, ratio);
215
+ // Headline label — varies by state so the user gets an immediate
216
+ // diagnostic signal at-a-glance.
217
+ let headline: string;
218
+ if (state === "none") {
219
+ headline = t.noCacheLabel;
220
+ } else if (state === "miss") {
221
+ headline = t.missLabel;
222
+ } else if (state === "priming") {
223
+ headline = t.writePrimingLabel;
224
+ } else {
225
+ // hot / warm / cold — ratio is guaranteed non-null here.
226
+ headline = `${t.hitLabel} ${Math.round((ratio ?? 0) * 100)}%`;
227
+ }
228
+ const tooltip = buildTooltip(usage, t);
229
+
230
+ return (
231
+ <span
232
+ className={cls.root}
233
+ data-testid="cache-badge"
234
+ data-cache-state={state}
235
+ title={tooltip}
236
+ role="status"
237
+ aria-label={tooltip}
238
+ >
239
+ <span className={cls.icon} aria-hidden="true">
240
+ 💾
241
+ </span>
242
+ <span className={cls.label}>{headline}</span>
243
+ </span>
244
+ );
245
+ }
@@ -0,0 +1,375 @@
1
+ /**
2
+ * `<DataClubAIHubChangePassword />` — the built-in change-password screen.
3
+ *
4
+ * Two variants in one component:
5
+ *
6
+ * - **Forced** (`variant="forced"`) — auto-rendered after login when
7
+ * `useAIHubAuth().mustChangePassword === true`. No `current_password`
8
+ * field, no cancel button (there is nowhere else to go), with an
9
+ * `intro_message` explaining the requirement. Submit calls
10
+ * `client.changePassword(null, newPassword)` — the change-only token
11
+ * is itself the proof of authentication.
12
+ * - **Voluntary** (`variant="voluntary"`) — opened from the user-menu
13
+ * ([00-shell.md](ui-sketches/00-shell.md)). Includes `current_password`
14
+ * and a `Cancel` button that fires `onCancel`. Submit calls
15
+ * `client.changePassword(currentPassword, newPassword)`.
16
+ *
17
+ * Each password field carries its own `show_password_toggle` — the
18
+ * regression-guard for the per-field pattern is in
19
+ * `__tests__/components/ChangePassword.test.tsx`.
20
+ *
21
+ * The client mirrors the §8.5 password policy via
22
+ * `validatePasswordPolicyClient` so the form surfaces too-short /
23
+ * too-simple errors *before* sending; the server is authoritative and
24
+ * its banner wins if the two ever disagree.
25
+ *
26
+ * @see SPECIFICATIONS.md §7.2.1 (and §7.3 — auth is owned by the library)
27
+ * @see SPECIFICATIONS.md §8.5 — password policy + forced/voluntary semantics
28
+ * @see ui-sketches/02-change-password.md — visual contract
29
+ */
30
+
31
+ import { useState, type FormEvent, type ReactNode } from "react";
32
+
33
+ import { AIHubRequestError } from "../../client/AIHubClient.js";
34
+ import { useAIHubAuth } from "../../context/useAIHubAuth.js";
35
+ import {
36
+ resolveAIHubTexts,
37
+ type AIHubTexts,
38
+ type DeepPartial,
39
+ } from "../../auth/texts.js";
40
+ import { validatePasswordPolicyClient } from "../../auth/passwordPolicy.js";
41
+ import { PasswordInput } from "../internal/PasswordInput.js";
42
+
43
+ /**
44
+ * BEM class names emitted by `<DataClubAIHubChangePassword />`. Wrappers
45
+ * pass a partial map via `classNames` to override individual entries for
46
+ * utility-CSS frameworks.
47
+ */
48
+ export interface AIHubChangePasswordClassNames {
49
+ /** Outer page wrapper (centers the card in the viewport). */
50
+ root: string;
51
+ /** The card itself. */
52
+ card: string;
53
+ /** Gradient title at the top. */
54
+ title: string;
55
+ /** Intro message (forced variant only). */
56
+ intro: string;
57
+ /** Error banner shown above the form on failure. */
58
+ errorBanner: string;
59
+ /** Form container. */
60
+ form: string;
61
+ /** Label / field group wrapper. */
62
+ fieldGroup: string;
63
+ /** `<label>` for an input. */
64
+ fieldLabel: string;
65
+ /** Inline field-level error message. */
66
+ fieldError: string;
67
+ /** Password input wrapper (input + show toggle). */
68
+ passwordWrapper: string;
69
+ /** The password `<input>` itself. */
70
+ passwordInput: string;
71
+ /** The show/hide-password toggle button. */
72
+ passwordToggle: string;
73
+ /** Submit button. */
74
+ submitButton: string;
75
+ /** Cancel button (voluntary variant only). */
76
+ cancelButton: string;
77
+ /** Spinner glyph rendered inside the submit button while saving. */
78
+ spinner: string;
79
+ }
80
+
81
+ /**
82
+ * Default BEM class names. Stable, kebab-case, scoped under
83
+ * `data-club-ai-hub__change-password`. Wrappers override individual entries
84
+ * via `classNames`.
85
+ */
86
+ const DEFAULT_CLASS_NAMES: AIHubChangePasswordClassNames = Object.freeze({
87
+ root: "data-club-ai-hub__change-password",
88
+ card: "data-club-ai-hub__change-password-card",
89
+ title: "data-club-ai-hub__change-password-card__title",
90
+ intro: "data-club-ai-hub__change-password-card__intro",
91
+ errorBanner: "data-club-ai-hub__change-password-card__error",
92
+ form: "data-club-ai-hub__change-password-card__form",
93
+ fieldGroup: "data-club-ai-hub__change-password-card__field",
94
+ fieldLabel: "data-club-ai-hub__change-password-card__label",
95
+ fieldError: "data-club-ai-hub__change-password-card__field-error",
96
+ passwordWrapper: "data-club-ai-hub__change-password-card__password",
97
+ passwordInput:
98
+ "data-club-ai-hub__change-password-card__input data-club-ai-hub__change-password-card__input--password",
99
+ passwordToggle: "data-club-ai-hub__change-password-card__toggle",
100
+ submitButton: "data-club-ai-hub__change-password-card__submit",
101
+ cancelButton: "data-club-ai-hub__change-password-card__cancel",
102
+ spinner: "data-club-ai-hub__spinner",
103
+ }) as AIHubChangePasswordClassNames;
104
+
105
+ /**
106
+ * Props for `<DataClubAIHubChangePassword />`. See §7.2.1.
107
+ */
108
+ export interface DataClubAIHubChangePasswordProps {
109
+ /**
110
+ * Which variant to render. **`"forced"`** (default — what `AuthGate`
111
+ * mounts after a successful login with `is_initial_password=true`) hides
112
+ * the current-password field + cancel button, and shows an intro
113
+ * message. **`"voluntary"`** (from the user-menu) shows all three fields
114
+ * + a cancel button.
115
+ */
116
+ variant?: "forced" | "voluntary";
117
+ /**
118
+ * Fires after a successful password change. The provider has already
119
+ * stored the fresh session token + refreshed `/auth/me` when this fires,
120
+ * so the caller can read the now-updated `useAIHubAuth().user` directly.
121
+ */
122
+ onChanged?: () => void;
123
+ /**
124
+ * Fires when the user clicks Cancel on the voluntary variant. Ignored on
125
+ * the forced variant (there is nowhere else to go).
126
+ */
127
+ onCancel?: () => void;
128
+ /** Extra class merged onto the root wrapper alongside the BEM defaults. */
129
+ className?: string;
130
+ /** Per-element class-name overrides. */
131
+ classNames?: Partial<AIHubChangePasswordClassNames>;
132
+ /**
133
+ * Wrapper-supplied i18n. Missing keys fall back to English defaults.
134
+ * Only the `changePassword` namespace is consulted by this component.
135
+ */
136
+ texts?: DeepPartial<AIHubTexts>;
137
+ }
138
+
139
+ /**
140
+ * Renders the change-password card.
141
+ *
142
+ * @param props - see `DataClubAIHubChangePasswordProps`.
143
+ * @returns the rendered card.
144
+ */
145
+ export function DataClubAIHubChangePassword(
146
+ props: DataClubAIHubChangePasswordProps,
147
+ ): ReactNode {
148
+ const auth = useAIHubAuth();
149
+ const variant = props.variant ?? "forced";
150
+ const classes = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
151
+ const texts = resolveAIHubTexts(props.texts).changePassword;
152
+
153
+ const [currentPassword, setCurrentPassword] = useState("");
154
+ const [newPassword, setNewPassword] = useState("");
155
+ const [confirmPassword, setConfirmPassword] = useState("");
156
+ const [submitting, setSubmitting] = useState(false);
157
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
158
+
159
+ // Client-side validation produces an inline field-error message — separate
160
+ // from the top-of-card error banner, which is reserved for server errors.
161
+ const fieldError: string | null = (() => {
162
+ if (newPassword.length === 0 && confirmPassword.length === 0) return null;
163
+ const policy = validatePasswordPolicyClient(newPassword);
164
+ if (!policy.ok) {
165
+ if (policy.code === "password_too_short") return texts.passwordTooShortError;
166
+ return texts.passwordTooSimpleError;
167
+ }
168
+ if (confirmPassword.length > 0 && newPassword !== confirmPassword) {
169
+ return texts.passwordsMustMatchError;
170
+ }
171
+ return null;
172
+ })();
173
+
174
+ // Submit-disable predicate: voluntary needs all three; forced needs two.
175
+ const fieldsFilled =
176
+ newPassword.length > 0 &&
177
+ confirmPassword.length > 0 &&
178
+ newPassword === confirmPassword &&
179
+ (variant === "forced" || currentPassword.length > 0);
180
+
181
+ const submitDisabled = submitting || !fieldsFilled || fieldError !== null;
182
+
183
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
184
+ e.preventDefault();
185
+ if (submitDisabled) return;
186
+
187
+ setSubmitting(true);
188
+ setErrorMessage(null);
189
+ try {
190
+ await auth.changePassword(
191
+ variant === "voluntary" ? currentPassword : null,
192
+ newPassword,
193
+ );
194
+ props.onChanged?.();
195
+ } catch (err) {
196
+ if (err instanceof AIHubRequestError) {
197
+ // Map the wire error code to a localized banner. Server's
198
+ // password-policy errors trump the client mirror — the server is
199
+ // authoritative (§8.5).
200
+ if (err.code === "password_too_short") {
201
+ setErrorMessage(texts.passwordTooShortError);
202
+ } else if (err.code === "password_too_simple") {
203
+ setErrorMessage(texts.passwordTooSimpleError);
204
+ } else if (err.code === "unauthorized" || err.status === 401) {
205
+ setErrorMessage(texts.invalidCurrentPasswordError);
206
+ } else if (err.code === "network") {
207
+ setErrorMessage(texts.networkError);
208
+ } else {
209
+ // Fall back to the server message — it's safe to surface per §9.
210
+ setErrorMessage(err.message || texts.networkError);
211
+ }
212
+ } else {
213
+ setErrorMessage(texts.networkError);
214
+ }
215
+ } finally {
216
+ setSubmitting(false);
217
+ }
218
+ };
219
+
220
+ const rootClass =
221
+ props.className !== undefined
222
+ ? `${classes.root} ${props.className}`
223
+ : classes.root;
224
+
225
+ return (
226
+ <div
227
+ className={rootClass}
228
+ data-testid="change-password-root"
229
+ data-variant={variant}
230
+ >
231
+ <div
232
+ className={classes.card}
233
+ role="form"
234
+ aria-labelledby="dc-aih-change-password-title"
235
+ >
236
+ <h1
237
+ id="dc-aih-change-password-title"
238
+ className={classes.title}
239
+ >
240
+ {texts.title}
241
+ </h1>
242
+
243
+ {variant === "forced" ? (
244
+ <p className={classes.intro} data-testid="change-password-intro">
245
+ {texts.forcedChangePasswordIntro}
246
+ </p>
247
+ ) : null}
248
+
249
+ {errorMessage !== null ? (
250
+ <div
251
+ className={classes.errorBanner}
252
+ role="alert"
253
+ aria-live="polite"
254
+ data-testid="change-password-error"
255
+ >
256
+ {errorMessage}
257
+ </div>
258
+ ) : null}
259
+
260
+ <form className={classes.form} onSubmit={handleSubmit} noValidate>
261
+ {variant === "voluntary" ? (
262
+ <div className={classes.fieldGroup}>
263
+ <label
264
+ htmlFor="dc-aih-change-current"
265
+ className={classes.fieldLabel}
266
+ >
267
+ {texts.currentPasswordLabel}
268
+ </label>
269
+ <PasswordInput
270
+ id="dc-aih-change-current"
271
+ value={currentPassword}
272
+ onChange={setCurrentPassword}
273
+ name="current_password"
274
+ autoComplete="current-password"
275
+ disabled={submitting}
276
+ required
277
+ autoFocus
278
+ showAriaLabel={texts.showPasswordAriaLabel}
279
+ hideAriaLabel={texts.hidePasswordAriaLabel}
280
+ wrapperClassName={classes.passwordWrapper}
281
+ inputClassName={classes.passwordInput}
282
+ toggleClassName={classes.passwordToggle}
283
+ testId="change-current-field"
284
+ />
285
+ </div>
286
+ ) : null}
287
+
288
+ <div className={classes.fieldGroup}>
289
+ <label htmlFor="dc-aih-change-new" className={classes.fieldLabel}>
290
+ {texts.newPasswordLabel}
291
+ </label>
292
+ <PasswordInput
293
+ id="dc-aih-change-new"
294
+ value={newPassword}
295
+ onChange={setNewPassword}
296
+ name="new_password"
297
+ autoComplete="new-password"
298
+ disabled={submitting}
299
+ required
300
+ {...(variant === "forced" ? { autoFocus: true } : {})}
301
+ showAriaLabel={texts.showPasswordAriaLabel}
302
+ hideAriaLabel={texts.hidePasswordAriaLabel}
303
+ wrapperClassName={classes.passwordWrapper}
304
+ inputClassName={classes.passwordInput}
305
+ toggleClassName={classes.passwordToggle}
306
+ testId="change-new-field"
307
+ />
308
+ </div>
309
+
310
+ <div className={classes.fieldGroup}>
311
+ <label
312
+ htmlFor="dc-aih-change-confirm"
313
+ className={classes.fieldLabel}
314
+ >
315
+ {texts.confirmPasswordLabel}
316
+ </label>
317
+ <PasswordInput
318
+ id="dc-aih-change-confirm"
319
+ value={confirmPassword}
320
+ onChange={setConfirmPassword}
321
+ name="confirm_password"
322
+ autoComplete="new-password"
323
+ disabled={submitting}
324
+ required
325
+ showAriaLabel={texts.showPasswordAriaLabel}
326
+ hideAriaLabel={texts.hidePasswordAriaLabel}
327
+ wrapperClassName={classes.passwordWrapper}
328
+ inputClassName={classes.passwordInput}
329
+ toggleClassName={classes.passwordToggle}
330
+ testId="change-confirm-field"
331
+ />
332
+ {fieldError !== null ? (
333
+ <div
334
+ className={classes.fieldError}
335
+ role="status"
336
+ aria-live="polite"
337
+ data-testid="change-password-field-error"
338
+ >
339
+ {fieldError}
340
+ </div>
341
+ ) : null}
342
+ </div>
343
+
344
+ <button
345
+ type="submit"
346
+ className={classes.submitButton}
347
+ disabled={submitDisabled}
348
+ data-testid="change-password-submit"
349
+ >
350
+ {submitting ? (
351
+ <>
352
+ <span className={classes.spinner} aria-hidden="true" />
353
+ {texts.savingButton}
354
+ </>
355
+ ) : (
356
+ texts.changePasswordButton
357
+ )}
358
+ </button>
359
+
360
+ {variant === "voluntary" ? (
361
+ <button
362
+ type="button"
363
+ className={classes.cancelButton}
364
+ onClick={() => props.onCancel?.()}
365
+ disabled={submitting}
366
+ data-testid="change-password-cancel"
367
+ >
368
+ {texts.cancelButton}
369
+ </button>
370
+ ) : null}
371
+ </form>
372
+ </div>
373
+ </div>
374
+ );
375
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * `<ShellHeader />` — the sticky top bar wrapping every authenticated
3
+ * view ([00-shell.md]).
4
+ *
5
+ * Lives in both the chat shell AND the admin shell — Step 11 split it
6
+ * out of `<ChatShell />` so the admin view doesn't lose the user-menu /
7
+ * log-out / theme toggle / Open Letta ADE affordances when the user
8
+ * navigates from chat to admin.
9
+ *
10
+ * Left side: wrapper-supplied slot (`customShellHeaderLeft`) or default
11
+ * gradient text logo derived from `productName`. **Right side:
12
+ * library-owned** — the `<UserMenu />` is non-customizable per
13
+ * [00-shell.md] because it carries session, role, and `Open Letta ADE`
14
+ * affordances ai-hub controls.
15
+ *
16
+ * @see ui-sketches/00-shell.md
17
+ */
18
+ import type { ReactNode } from "react";
19
+
20
+ import type { AIHubChatTexts } from "../../auth/texts.js";
21
+
22
+ import { UserMenu } from "./UserMenu.js";
23
+
24
+ /**
25
+ * Props for `<ShellHeader />`.
26
+ */
27
+ export interface ShellHeaderProps {
28
+ /** Default-logo source (when `customShellHeaderLeft` is unset). */
29
+ productName: string;
30
+ /** Wrapper-supplied logo / nav-links slot. */
31
+ customShellHeaderLeft?: ReactNode;
32
+ /** Resolved chat texts (the user-menu copy lives under `texts.chat`). */
33
+ texts: AIHubChatTexts;
34
+ /**
35
+ * Fired when the user picks "Admin" from the user menu. Wired to the
36
+ * monolith's chat ↔ admin route swap; omitted by Tier-3 wrappers that
37
+ * handle navigation themselves.
38
+ */
39
+ onOpenAdmin?: () => void;
40
+ }
41
+
42
+ /**
43
+ * Renders the sticky shell header.
44
+ *
45
+ * @param props - see `ShellHeaderProps`.
46
+ * @returns the rendered header.
47
+ */
48
+ export function ShellHeader(props: ShellHeaderProps): ReactNode {
49
+ return (
50
+ <header
51
+ className="data-club-ai-hub__shell-header"
52
+ data-testid="shell-header"
53
+ >
54
+ <div
55
+ className="data-club-ai-hub__shell-header-left"
56
+ data-testid="shell-header-left"
57
+ >
58
+ {props.customShellHeaderLeft ?? (
59
+ <span
60
+ className="data-club-ai-hub__shell-header-logo"
61
+ data-testid="shell-header-default-logo"
62
+ >
63
+ {props.productName}
64
+ </span>
65
+ )}
66
+ </div>
67
+ <div
68
+ className="data-club-ai-hub__shell-header-right"
69
+ data-testid="shell-header-right"
70
+ >
71
+ <UserMenu
72
+ texts={props.texts}
73
+ {...(props.onOpenAdmin !== undefined
74
+ ? { onOpenAdmin: props.onOpenAdmin }
75
+ : {})}
76
+ />
77
+ </div>
78
+ </header>
79
+ );
80
+ }