@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,277 @@
1
+ /**
2
+ * `<DataClubAIHubAdmin />` — role-gated admin UI shell (§8.3,
3
+ * [04-admin-overview.md]).
4
+ *
5
+ * Step 11 ships the shell + the Users sub-page; Step 12 fills in Agents,
6
+ * Sessions overview, and Settings. Nav items the caller's role isn't
7
+ * authorized for are **hidden, not greyed** — per §8.3. When mounted by a
8
+ * `user`-role caller the component returns `null` outright.
9
+ *
10
+ * The shell **renders its own `<ShellHeader />`** so the user-menu (log
11
+ * out, theme toggle, Open Letta ADE, ← Back to chat) stays available
12
+ * from inside the admin view too — [00-shell.md] makes the shell header
13
+ * part of every authenticated page. The component is identical to the
14
+ * one ChatShell renders.
15
+ *
16
+ * The active sub-page is derived from the role + an optional explicit
17
+ * user click (see `activePage` below) — it is NOT eagerly initialized
18
+ * from the role because `auth.role` may be `null` during hydration.
19
+ *
20
+ * @see SPECIFICATIONS.md §8.3 — admin views
21
+ * @see ui-sketches/04-admin-overview.md — nav shell
22
+ * @see ui-sketches/05-admin-users.md — Users sub-page
23
+ * @see ui-sketches/00-shell.md — header chrome
24
+ */
25
+ import { useMemo, useState, type ReactNode } from "react";
26
+
27
+ import {
28
+ resolveAIHubTexts,
29
+ type AIHubTexts,
30
+ type DeepPartial,
31
+ } from "../../auth/texts.js";
32
+ import { useAIHub } from "../../context/useAIHub.js";
33
+ import { useAIHubAuth } from "../../context/useAIHubAuth.js";
34
+ import type { AIHubRole } from "../../types.js";
35
+
36
+ import { ShellHeader } from "../ChatShell/ShellHeader.js";
37
+
38
+ import { AdminAgentsPage } from "./AgentsPage.js";
39
+ import { AdminRotatingMessagesPage } from "./RotatingMessagesPage.js";
40
+ import { AdminSessionsOverviewPage } from "./SessionsOverviewPage.js";
41
+ import { AdminSettingsPage } from "./SettingsPage.js";
42
+ import { AdminUsersPage } from "./UsersPage.js";
43
+
44
+ /**
45
+ * One of the admin sub-pages identified in [04-admin-overview.md]. `rotating`
46
+ * (rotating starting messages) was added alongside Agents — same `power_user`+
47
+ * visibility.
48
+ *
49
+ * @see ui-sketches/04-admin-overview.md
50
+ */
51
+ export type AdminPage = "users" | "agents" | "rotating" | "sessions" | "settings";
52
+
53
+ /**
54
+ * Props for `<DataClubAIHubAdmin />`.
55
+ *
56
+ * @see SPECIFICATIONS.md §8.3
57
+ */
58
+ export interface DataClubAIHubAdminProps {
59
+ /** Wrapper-supplied i18n. Falls back to English defaults for missing keys. */
60
+ texts?: DeepPartial<AIHubTexts>;
61
+ /**
62
+ * Overrides the initial sub-page. Defaults to `users` for `admin` /
63
+ * `super_admin` and `agents` for `power_user` per [04-admin-overview.md].
64
+ * When the supplied initial sub-page isn't authorized for the caller's
65
+ * role, falls back to the same default-by-role logic.
66
+ */
67
+ initialPage?: AdminPage;
68
+ /**
69
+ * Optional callback fired when the user clicks back to chat. The monolith
70
+ * (`<DataClubAIHub />`) supplies one that swaps the admin view back out;
71
+ * standalone mounts can leave it unset.
72
+ */
73
+ onExit?: () => void;
74
+ /** Extra className merged onto the admin root. */
75
+ className?: string;
76
+ /**
77
+ * Default-logo source surfaced through the shell header when
78
+ * `customShellHeaderLeft` is unset. Defaults to `"ai-hub"`. The monolith
79
+ * forwards its own `productName` here so the admin shell brands
80
+ * consistently with chat.
81
+ */
82
+ productName?: string;
83
+ /**
84
+ * Wrapper-supplied logo / nav-links slot for the shell header. The
85
+ * monolith forwards `customShellHeaderLeft` here so the admin shell
86
+ * matches the chat shell's chrome.
87
+ */
88
+ customShellHeaderLeft?: ReactNode;
89
+ }
90
+
91
+ /**
92
+ * Per-role nav-item visibility per the §8.3 / [04-admin-overview.md] matrix.
93
+ *
94
+ * `super_admin` sees everything; `admin` loses Settings; `power_user` also
95
+ * loses Users; `user` returns `null` upstream.
96
+ */
97
+ function navItemsForRole(role: AIHubRole | null): AdminPage[] {
98
+ if (role === "super_admin") {
99
+ return ["users", "agents", "rotating", "sessions", "settings"];
100
+ }
101
+ if (role === "admin") {
102
+ return ["users", "agents", "rotating", "sessions"];
103
+ }
104
+ if (role === "power_user") {
105
+ return ["agents", "rotating", "sessions"];
106
+ }
107
+ // `user` callers don't render this component at all (see early return).
108
+ return [];
109
+ }
110
+
111
+ /**
112
+ * Default landing page per role per [04-admin-overview.md] notes:
113
+ * `Users` for admin / super_admin, `Agents` for power_user.
114
+ */
115
+ function defaultPageForRole(role: AIHubRole | null): AdminPage {
116
+ if (role === "power_user") return "agents";
117
+ return "users";
118
+ }
119
+
120
+ interface NavLabels {
121
+ heading: string;
122
+ users: string;
123
+ agents: string;
124
+ rotating: string;
125
+ sessions: string;
126
+ settings: string;
127
+ }
128
+
129
+ /**
130
+ * Renders the admin shell (nav + content) or `null` for unauthorized callers.
131
+ *
132
+ * Behavior at a glance:
133
+ *
134
+ * - Returns `null` when the caller is `user`-role or unauthenticated.
135
+ * - Defaults to `users` (admin / super_admin) or `agents` (power_user); the
136
+ * `initialPage` prop overrides only when it's authorized for the caller.
137
+ * - Sub-pages other than Users render the "coming in Step 12" placeholder
138
+ * for now — Step 12 fills them in.
139
+ *
140
+ * @param props - see `DataClubAIHubAdminProps`.
141
+ * @returns the rendered admin UI, or `null` when the caller can't see it.
142
+ */
143
+ export function DataClubAIHubAdmin(
144
+ props: DataClubAIHubAdminProps,
145
+ ): ReactNode {
146
+ const auth = useAIHubAuth();
147
+ // `theme` is forwarded as `data-theme` on the admin root so the dark
148
+ // theme's `[data-theme="dark"]` selector matches here too — otherwise
149
+ // the root's own `.data-club-ai-hub-admin` rule re-sets the light
150
+ // custom-property values and overrides the dark cascade from the
151
+ // provider's outer wrapper.
152
+ const { theme } = useAIHub();
153
+ const texts = resolveAIHubTexts(props.texts);
154
+ const navItems = navItemsForRole(auth.role);
155
+ const productName = props.productName ?? "ai-hub";
156
+
157
+ // `explicitPage` is `null` until the user clicks a nav item. We DON'T
158
+ // initialize from `auth.role` because the role may still be `null` while
159
+ // the auth context is hydrating; deriving `activePage` inline below keeps
160
+ // the page in sync with the role even after late hydration.
161
+ const [explicitPage, setExplicitPage] = useState<AdminPage | null>(
162
+ props.initialPage ?? null,
163
+ );
164
+ // Derive the active page from (in order): explicit user click, `initialPage`
165
+ // prop, role-derived default. The final guard demotes a stale `explicitPage`
166
+ // that the caller no longer has access to (e.g. role demotion between
167
+ // clicks) — without it the AdminUsersPage would mount for a power_user.
168
+ const activePage: AdminPage =
169
+ explicitPage !== null && navItems.includes(explicitPage)
170
+ ? explicitPage
171
+ : defaultPageForRole(auth.role);
172
+ const setActivePage = (page: AdminPage): void => {
173
+ setExplicitPage(page);
174
+ };
175
+
176
+ const labels: NavLabels = useMemo(
177
+ () => ({
178
+ heading: texts.admin.navHeading,
179
+ users: texts.admin.navUsers,
180
+ agents: texts.admin.navAgents,
181
+ rotating: texts.admin.navRotatingMessages,
182
+ sessions: texts.admin.navSessionsOverview,
183
+ settings: texts.admin.navSettings,
184
+ }),
185
+ [texts],
186
+ );
187
+
188
+ // Hide entirely for unauthorized callers (and while the auth context is
189
+ // still hydrating — the shell would flash if rendered first).
190
+ if (auth.hydrating) return null;
191
+ if (!auth.user || auth.role === null) return null;
192
+ if (auth.role === "user") return null;
193
+
194
+ const rootClass = props.className
195
+ ? `data-club-ai-hub-admin ${props.className}`
196
+ : "data-club-ai-hub-admin";
197
+
198
+ return (
199
+ <div className={rootClass} data-testid="admin-root" data-theme={theme}>
200
+ <ShellHeader
201
+ productName={productName}
202
+ texts={texts.chat}
203
+ {...(props.customShellHeaderLeft !== undefined
204
+ ? { customShellHeaderLeft: props.customShellHeaderLeft }
205
+ : {})}
206
+ />
207
+ <div
208
+ className="data-club-ai-hub-admin__body"
209
+ data-testid="admin-body"
210
+ >
211
+ <aside
212
+ className="data-club-ai-hub-admin__nav"
213
+ data-testid="admin-nav"
214
+ aria-label={labels.heading}
215
+ >
216
+ <div className="data-club-ai-hub-admin__nav-heading">
217
+ {labels.heading}
218
+ </div>
219
+ <nav className="data-club-ai-hub-admin__nav-list" role="navigation">
220
+ {navItems.map((item) => (
221
+ <button
222
+ key={item}
223
+ type="button"
224
+ role="link"
225
+ className={`data-club-ai-hub-admin__nav-item${
226
+ activePage === item
227
+ ? " data-club-ai-hub-admin__nav-item--active"
228
+ : ""
229
+ }`}
230
+ data-testid={`admin-nav-${item}`}
231
+ aria-current={activePage === item ? "page" : undefined}
232
+ onClick={() => setActivePage(item)}
233
+ >
234
+ {item === "users"
235
+ ? labels.users
236
+ : item === "agents"
237
+ ? labels.agents
238
+ : item === "rotating"
239
+ ? labels.rotating
240
+ : item === "sessions"
241
+ ? labels.sessions
242
+ : labels.settings}
243
+ </button>
244
+ ))}
245
+ </nav>
246
+ {props.onExit ? (
247
+ <button
248
+ type="button"
249
+ className="data-club-ai-hub-admin__nav-exit"
250
+ data-testid="admin-nav-exit"
251
+ onClick={props.onExit}
252
+ >
253
+ ← {texts.admin.backToChatLink}
254
+ </button>
255
+ ) : null}
256
+ </aside>
257
+ <section
258
+ className="data-club-ai-hub-admin__content"
259
+ data-testid="admin-content"
260
+ >
261
+ {activePage === "users" ? (
262
+ <AdminUsersPage texts={texts.admin} />
263
+ ) : activePage === "agents" ? (
264
+ <AdminAgentsPage texts={texts.admin} />
265
+ ) : activePage === "rotating" ? (
266
+ <AdminRotatingMessagesPage texts={texts.admin} />
267
+ ) : activePage === "sessions" ? (
268
+ <AdminSessionsOverviewPage texts={texts.admin} />
269
+ ) : (
270
+ <AdminSettingsPage texts={texts.admin} />
271
+ )}
272
+ </section>
273
+ </div>
274
+ </div>
275
+ );
276
+ }
277
+
@@ -0,0 +1,282 @@
1
+ /**
2
+ * `<DataClubAIHubLogin />` — the built-in login screen.
3
+ *
4
+ * Auto-rendered by `<DataClubAIHub />` (and `AIHub.AuthGate`) when no
5
+ * valid session token is present. Per §7.2.1 + §7.3 + §8.5, this screen
6
+ * is **library-owned** — there is no escape hatch in v1 to substitute a
7
+ * custom React component. Wrappers brand it via `productName` /
8
+ * `usernamePlaceholder` props, `texts` for localization, the BEM class
9
+ * contract, and CSS custom properties on `.data-club-ai-hub`.
10
+ *
11
+ * Behavior summary:
12
+ *
13
+ * - Submit → `useAIHubAuth().login(username, password)`.
14
+ * - On success with `mustChangePassword === false` → fires `onLoggedIn(user)`.
15
+ * `AIHub.AuthGate` listens to `mustChangePassword` and routes to the
16
+ * forced-change screen automatically; this component does not navigate
17
+ * on its own.
18
+ * - On failure → shows the `error_message` banner above the form; inputs
19
+ * stay editable.
20
+ * - No "Forgot password?" link, no "Sign up" link — per v1 spec; the
21
+ * `forgot_password_note` is plain helper text.
22
+ *
23
+ * @see SPECIFICATIONS.md §7.2.1 — `<DataClubAIHubLogin />` props
24
+ * @see SPECIFICATIONS.md §8.5 — login flow + initial-password handling
25
+ * @see ui-sketches/01-login.md — visual contract for every region
26
+ */
27
+
28
+ import { useState, type FormEvent, type ReactNode } from "react";
29
+
30
+ import { useAIHubAuth } from "../../context/useAIHubAuth.js";
31
+ import { AIHubRequestError } from "../../client/AIHubClient.js";
32
+ import { resolveAIHubTexts, type AIHubTexts, type DeepPartial } from "../../auth/texts.js";
33
+ import type { AIHubUser } from "../../types.js";
34
+ import { PasswordInput } from "../internal/PasswordInput.js";
35
+
36
+ /**
37
+ * BEM class names emitted by `<DataClubAIHubLogin />`. Per-customer wrappers
38
+ * pass a partial of this map via the `classNames` prop to override individual
39
+ * names for utility-CSS frameworks.
40
+ */
41
+ export interface AIHubLoginClassNames {
42
+ /** Outer page wrapper (centers the card in the viewport). */
43
+ root: string;
44
+ /** The card itself. */
45
+ card: string;
46
+ /** Gradient title (product name). */
47
+ title: string;
48
+ /** Error banner shown above the form on failure. */
49
+ errorBanner: string;
50
+ /** Form container. */
51
+ form: string;
52
+ /** Label / field group wrapper. */
53
+ fieldGroup: string;
54
+ /** `<label>` for an input. */
55
+ fieldLabel: string;
56
+ /** Plain text inputs (username). */
57
+ textInput: string;
58
+ /** Password input wrapper (input + show toggle). */
59
+ passwordWrapper: string;
60
+ /** The password `<input>` itself. */
61
+ passwordInput: string;
62
+ /** The show/hide-password toggle button. */
63
+ passwordToggle: string;
64
+ /** Submit button. */
65
+ submitButton: string;
66
+ /** Spinner glyph rendered inside the submit button while submitting. */
67
+ spinner: string;
68
+ /** "Forgot password?" helper note below the submit button. */
69
+ forgotNote: string;
70
+ }
71
+
72
+ /**
73
+ * Default BEM class names. Stable, kebab-case, scoped under
74
+ * `data-club-ai-hub__login`. Wrappers override individual entries via
75
+ * the `classNames` prop.
76
+ */
77
+ const DEFAULT_CLASS_NAMES: AIHubLoginClassNames = Object.freeze({
78
+ root: "data-club-ai-hub__login",
79
+ card: "data-club-ai-hub__login-card",
80
+ title: "data-club-ai-hub__login-card__title",
81
+ errorBanner: "data-club-ai-hub__login-card__error",
82
+ form: "data-club-ai-hub__login-card__form",
83
+ fieldGroup: "data-club-ai-hub__login-card__field",
84
+ fieldLabel: "data-club-ai-hub__login-card__label",
85
+ textInput: "data-club-ai-hub__login-card__input",
86
+ passwordWrapper: "data-club-ai-hub__login-card__password",
87
+ passwordInput: "data-club-ai-hub__login-card__input data-club-ai-hub__login-card__input--password",
88
+ passwordToggle: "data-club-ai-hub__login-card__toggle",
89
+ submitButton: "data-club-ai-hub__login-card__submit",
90
+ spinner: "data-club-ai-hub__spinner",
91
+ forgotNote: "data-club-ai-hub__login-card__forgot",
92
+ }) as AIHubLoginClassNames;
93
+
94
+ /**
95
+ * Props for `<DataClubAIHubLogin />`. See §7.2.1.
96
+ */
97
+ export interface DataClubAIHubLoginProps {
98
+ /**
99
+ * Called after a successful login *only* when the server returned
100
+ * `mustChangePassword === false`. For the forced-change path,
101
+ * `useAIHubAuth().mustChangePassword` flips to `true` and `AuthGate`
102
+ * routes to the change-password screen — this callback is not fired.
103
+ */
104
+ onLoggedIn?: (user: AIHubUser) => void;
105
+ /** Extra class merged onto the root wrapper alongside the BEM defaults. */
106
+ className?: string;
107
+ /** Per-element class-name overrides for utility-CSS frameworks. */
108
+ classNames?: Partial<AIHubLoginClassNames>;
109
+ /**
110
+ * Title text shown at the top of the card. Defaults to `"ai-hub"`. The
111
+ * per-customer wrapper passes the brand label (e.g. `"BankXYZ Chat"`).
112
+ */
113
+ productName?: string;
114
+ /**
115
+ * Placeholder shown inside the username input. Defaults to empty.
116
+ * The seed-time hint `"DataClub"` is no longer baked in — operators
117
+ * who want it pass it explicitly during first-time-setup and remove it
118
+ * once the customer has rotated to their own users.
119
+ */
120
+ usernamePlaceholder?: string;
121
+ /**
122
+ * Wrapper-supplied i18n. Missing keys fall back to English defaults. Only
123
+ * the `login` namespace is consulted by this component; the
124
+ * `changePassword` namespace is consumed by the change-password screen.
125
+ *
126
+ * Accepts the full `AIHubTexts` shape (so a wrapper can pass one object
127
+ * to every built-in screen) — only the `login` keys are read here.
128
+ */
129
+ texts?: DeepPartial<AIHubTexts>;
130
+ }
131
+
132
+ /**
133
+ * Renders the login card.
134
+ *
135
+ * Always reads auth state from the nearest `<AIHubProvider>` — there is no
136
+ * "controlled" mode where a parent supplies the username / password / submit
137
+ * handler.
138
+ *
139
+ * @param props - see `DataClubAIHubLoginProps`.
140
+ * @returns the rendered card.
141
+ */
142
+ export function DataClubAIHubLogin(props: DataClubAIHubLoginProps): ReactNode {
143
+ const auth = useAIHubAuth();
144
+ const classes = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
145
+ const texts = resolveAIHubTexts(props.texts).login;
146
+ const productName = props.productName ?? "ai-hub";
147
+
148
+ const [username, setUsername] = useState("");
149
+ const [password, setPassword] = useState("");
150
+ const [submitting, setSubmitting] = useState(false);
151
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
152
+
153
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
154
+ e.preventDefault();
155
+ if (submitting) return;
156
+ if (username.length === 0 || password.length === 0) return;
157
+
158
+ setSubmitting(true);
159
+ setErrorMessage(null);
160
+ try {
161
+ const result = await auth.login(username, password);
162
+ // AuthGate watches `mustChangePassword` to switch screens — this
163
+ // component just notifies the parent when the login was definitive.
164
+ // Reading the user from `result` (not from `auth.user`) avoids the
165
+ // stale-closure pitfall: the surrounding `auth` snapshot was taken
166
+ // before `setUser` re-rendered the provider, so `auth.user` here is
167
+ // still null.
168
+ if (!result.mustChangePassword && props.onLoggedIn) {
169
+ props.onLoggedIn(result.user);
170
+ }
171
+ } catch (err) {
172
+ if (err instanceof AIHubRequestError) {
173
+ // Map the wire error code to a localized banner.
174
+ if (err.code === "unauthorized" || err.status === 401) {
175
+ setErrorMessage(texts.invalidCredentialsError);
176
+ } else if (err.code === "network") {
177
+ setErrorMessage(texts.networkError);
178
+ } else {
179
+ // Fall back to the server's message — it's safe to surface per §9.
180
+ setErrorMessage(err.message || texts.invalidCredentialsError);
181
+ }
182
+ } else {
183
+ setErrorMessage(texts.networkError);
184
+ }
185
+ } finally {
186
+ setSubmitting(false);
187
+ }
188
+ };
189
+
190
+ const submitDisabled =
191
+ submitting || username.length === 0 || password.length === 0;
192
+
193
+ const rootClass =
194
+ props.className !== undefined
195
+ ? `${classes.root} ${props.className}`
196
+ : classes.root;
197
+
198
+ return (
199
+ <div className={rootClass} data-testid="login-root">
200
+ <div className={classes.card} role="form" aria-labelledby="dc-aih-login-title">
201
+ <h1 id="dc-aih-login-title" className={classes.title}>
202
+ {productName}
203
+ </h1>
204
+
205
+ {errorMessage !== null ? (
206
+ <div
207
+ className={classes.errorBanner}
208
+ role="alert"
209
+ aria-live="polite"
210
+ data-testid="login-error"
211
+ >
212
+ {errorMessage}
213
+ </div>
214
+ ) : null}
215
+
216
+ <form className={classes.form} onSubmit={handleSubmit} noValidate>
217
+ <div className={classes.fieldGroup}>
218
+ <label htmlFor="dc-aih-login-username" className={classes.fieldLabel}>
219
+ {texts.usernameLabel}
220
+ </label>
221
+ <input
222
+ id="dc-aih-login-username"
223
+ type="text"
224
+ name="username"
225
+ autoComplete="username"
226
+ value={username}
227
+ onChange={(e) => setUsername(e.target.value)}
228
+ {...(props.usernamePlaceholder
229
+ ? { placeholder: props.usernamePlaceholder }
230
+ : {})}
231
+ disabled={submitting}
232
+ required
233
+ autoFocus
234
+ className={classes.textInput}
235
+ />
236
+ </div>
237
+
238
+ <div className={classes.fieldGroup}>
239
+ <label htmlFor="dc-aih-login-password" className={classes.fieldLabel}>
240
+ {texts.passwordLabel}
241
+ </label>
242
+ <PasswordInput
243
+ id="dc-aih-login-password"
244
+ value={password}
245
+ onChange={setPassword}
246
+ name="password"
247
+ autoComplete="current-password"
248
+ disabled={submitting}
249
+ required
250
+ showAriaLabel={texts.showPasswordAriaLabel}
251
+ hideAriaLabel={texts.hidePasswordAriaLabel}
252
+ wrapperClassName={classes.passwordWrapper}
253
+ inputClassName={classes.passwordInput}
254
+ toggleClassName={classes.passwordToggle}
255
+ testId="login-password-field"
256
+ />
257
+ </div>
258
+
259
+ <button
260
+ type="submit"
261
+ className={classes.submitButton}
262
+ disabled={submitDisabled}
263
+ data-testid="login-submit"
264
+ >
265
+ {submitting ? (
266
+ <>
267
+ <span className={classes.spinner} aria-hidden="true" />
268
+ {texts.signingInButton}
269
+ </>
270
+ ) : (
271
+ texts.signInButton
272
+ )}
273
+ </button>
274
+ </form>
275
+
276
+ <p className={classes.forgotNote} data-testid="login-forgot-note">
277
+ {texts.forgotPasswordNote}
278
+ </p>
279
+ </div>
280
+ </div>
281
+ );
282
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `<EffortControl />` — the per-message reasoning-effort dropdown.
3
+ *
4
+ * Mirrors Letta's ADE enum exactly: `None | Minimal | Low | Medium | High |
5
+ * Extra High` (snake-cased on the wire — `extra_high`). **`None` is the
6
+ * off state** for reasoning; there is no separate thinking/reasoning
7
+ * toggle. Per-message: the user changes effort, the next `sendMessage`
8
+ * picks it up, and the value sticks until the user changes it again
9
+ * (within the session — `setEffort` lives on the provider).
10
+ *
11
+ * Renders nothing when `effortControlVisible === false` upstream
12
+ * (server toggle wins over the React prop per §8.4). The component
13
+ * accepts the resolved boolean as an explicit prop so the predicate
14
+ * isn't re-derived at every render site.
15
+ *
16
+ * Exported under `AIHub.EffortControl`.
17
+ *
18
+ * @see SPECIFICATIONS.md §7.4 — reasoning effort
19
+ * @see ui-sketches/03-chat.md — effort_control
20
+ */
21
+
22
+ import { type ChangeEvent, type ReactNode } from "react";
23
+
24
+ import {
25
+ resolveAIHubTexts,
26
+ type AIHubTexts,
27
+ type DeepPartial,
28
+ } from "../../auth/texts.js";
29
+ import type { AIHubEffort } from "../../client/AIHubClient.js";
30
+ import { useAIHub } from "../../context/useAIHub.js";
31
+
32
+ /** BEM class names emitted by `<EffortControl />`. */
33
+ export interface EffortControlClassNames {
34
+ /** The label-wrapping container. */
35
+ root: string;
36
+ /** The label text. */
37
+ label: string;
38
+ /** The select element. */
39
+ select: string;
40
+ }
41
+
42
+ const DEFAULT_CLASS_NAMES: EffortControlClassNames = Object.freeze({
43
+ root: "data-club-ai-hub__effort-control",
44
+ label: "data-club-ai-hub__effort-control__label",
45
+ select: "data-club-ai-hub__effort-control__select",
46
+ }) as EffortControlClassNames;
47
+
48
+ /**
49
+ * Ordered list of every effort value — used by the renderer + by tests
50
+ * that want to assert the dropdown's option set without re-deriving the
51
+ * order.
52
+ */
53
+ export const EFFORT_VALUES: ReadonlyArray<AIHubEffort> = Object.freeze([
54
+ "none",
55
+ "minimal",
56
+ "low",
57
+ "medium",
58
+ "high",
59
+ "extra_high",
60
+ ]);
61
+
62
+ /**
63
+ * Resolves the i18n label for a given effort value.
64
+ *
65
+ * @param value - the effort enum value.
66
+ * @param texts - resolved chat texts.
67
+ * @returns the display label.
68
+ */
69
+ function labelForEffort(
70
+ value: AIHubEffort,
71
+ texts: AIHubTexts["chat"],
72
+ ): string {
73
+ switch (value) {
74
+ case "none":
75
+ return texts.effortNone;
76
+ case "minimal":
77
+ return texts.effortMinimal;
78
+ case "low":
79
+ return texts.effortLow;
80
+ case "medium":
81
+ return texts.effortMedium;
82
+ case "high":
83
+ return texts.effortHigh;
84
+ case "extra_high":
85
+ return texts.effortExtraHigh;
86
+ }
87
+ }
88
+
89
+ /** Props for `<EffortControl />`. */
90
+ export interface EffortControlProps {
91
+ /** Disable the control (e.g. while a turn is streaming). Default false. */
92
+ disabled?: boolean;
93
+ /** Wrapper-supplied i18n. */
94
+ texts?: DeepPartial<AIHubTexts>;
95
+ /** Extra class merged onto the root. */
96
+ className?: string;
97
+ /** Per-element class overrides. */
98
+ classNames?: Partial<EffortControlClassNames>;
99
+ }
100
+
101
+ /**
102
+ * Renders the effort dropdown.
103
+ *
104
+ * @param props - see `EffortControlProps`.
105
+ * @returns the rendered dropdown.
106
+ */
107
+ export function EffortControl(props: EffortControlProps): ReactNode {
108
+ const { effort, setEffort } = useAIHub();
109
+ const texts = resolveAIHubTexts(props.texts);
110
+ const cls = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
111
+ const rootClass = props.className
112
+ ? `${cls.root} ${props.className}`
113
+ : cls.root;
114
+
115
+ function handleChange(e: ChangeEvent<HTMLSelectElement>): void {
116
+ setEffort(e.target.value as AIHubEffort);
117
+ }
118
+
119
+ return (
120
+ <label className={rootClass} data-testid="effort-control">
121
+ <span className={cls.label}>{texts.chat.effortLabel}</span>
122
+ <select
123
+ className={cls.select}
124
+ value={effort}
125
+ onChange={handleChange}
126
+ disabled={props.disabled}
127
+ data-testid="effort-control-select"
128
+ aria-label={texts.chat.effortLabel}
129
+ >
130
+ {EFFORT_VALUES.map((value) => (
131
+ <option key={value} value={value}>
132
+ {labelForEffort(value, texts.chat)}
133
+ </option>
134
+ ))}
135
+ </select>
136
+ </label>
137
+ );
138
+ }