@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.
- package/dist/auth/passwordPolicy.d.ts +55 -0
- package/dist/auth/passwordPolicy.d.ts.map +1 -0
- package/dist/auth/passwordPolicy.js +63 -0
- package/dist/auth/passwordPolicy.js.map +1 -0
- package/dist/auth/texts.d.ts +815 -0
- package/dist/auth/texts.d.ts.map +1 -0
- package/dist/auth/texts.js +429 -0
- package/dist/auth/texts.js.map +1 -0
- package/dist/client/AIHubClient.d.ts +1058 -0
- package/dist/client/AIHubClient.d.ts.map +1 -0
- package/dist/client/AIHubClient.js +950 -0
- package/dist/client/AIHubClient.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/sseParser.d.ts +41 -0
- package/dist/client/sseParser.d.ts.map +1 -0
- package/dist/client/sseParser.js +153 -0
- package/dist/client/sseParser.js.map +1 -0
- package/dist/client/tokenStorage.d.ts +56 -0
- package/dist/client/tokenStorage.d.ts.map +1 -0
- package/dist/client/tokenStorage.js +116 -0
- package/dist/client/tokenStorage.js.map +1 -0
- package/dist/components/AgentsPicker/index.d.ts +70 -0
- package/dist/components/AgentsPicker/index.d.ts.map +1 -0
- package/dist/components/AgentsPicker/index.js +63 -0
- package/dist/components/AgentsPicker/index.js.map +1 -0
- package/dist/components/AuthGate/index.d.ts +79 -0
- package/dist/components/AuthGate/index.d.ts.map +1 -0
- package/dist/components/AuthGate/index.js +61 -0
- package/dist/components/AuthGate/index.js.map +1 -0
- package/dist/components/CacheBadge/index.d.ts +88 -0
- package/dist/components/CacheBadge/index.d.ts.map +1 -0
- package/dist/components/CacheBadge/index.js +127 -0
- package/dist/components/CacheBadge/index.js.map +1 -0
- package/dist/components/ChangePassword/index.d.ts +109 -0
- package/dist/components/ChangePassword/index.d.ts.map +1 -0
- package/dist/components/ChangePassword/index.js +142 -0
- package/dist/components/ChangePassword/index.js.map +1 -0
- package/dist/components/ChatShell/ShellHeader.d.ts +44 -0
- package/dist/components/ChatShell/ShellHeader.d.ts.map +1 -0
- package/dist/components/ChatShell/ShellHeader.js +14 -0
- package/dist/components/ChatShell/ShellHeader.js.map +1 -0
- package/dist/components/ChatShell/UserMenu.d.ts +48 -0
- package/dist/components/ChatShell/UserMenu.d.ts.map +1 -0
- package/dist/components/ChatShell/UserMenu.js +86 -0
- package/dist/components/ChatShell/UserMenu.js.map +1 -0
- package/dist/components/ChatShell/index.d.ts +98 -0
- package/dist/components/ChatShell/index.d.ts.map +1 -0
- package/dist/components/ChatShell/index.js +124 -0
- package/dist/components/ChatShell/index.js.map +1 -0
- package/dist/components/DataClubAIHub/index.d.ts +132 -0
- package/dist/components/DataClubAIHub/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHub/index.js +124 -0
- package/dist/components/DataClubAIHub/index.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts +89 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js +199 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts +33 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.js +137 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts +50 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.js +93 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts +49 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js +94 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts +37 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js +44 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts +56 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js +158 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts +29 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js +183 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts +38 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js +294 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts +36 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js +373 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts +47 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.js +233 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/index.d.ts +87 -0
- package/dist/components/DataClubAIHubAdmin/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/index.js +137 -0
- package/dist/components/DataClubAIHubAdmin/index.js.map +1 -0
- package/dist/components/DataClubAIHubLogin/index.d.ts +113 -0
- package/dist/components/DataClubAIHubLogin/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHubLogin/index.js +123 -0
- package/dist/components/DataClubAIHubLogin/index.js.map +1 -0
- package/dist/components/EffortControl/index.d.ts +57 -0
- package/dist/components/EffortControl/index.d.ts.map +1 -0
- package/dist/components/EffortControl/index.js +84 -0
- package/dist/components/EffortControl/index.js.map +1 -0
- package/dist/components/Layout/index.d.ts +63 -0
- package/dist/components/Layout/index.d.ts.map +1 -0
- package/dist/components/Layout/index.js +45 -0
- package/dist/components/Layout/index.js.map +1 -0
- package/dist/components/MainColumn/index.d.ts +69 -0
- package/dist/components/MainColumn/index.d.ts.map +1 -0
- package/dist/components/MainColumn/index.js +47 -0
- package/dist/components/MainColumn/index.js.map +1 -0
- package/dist/components/MarkdownRenderer/index.d.ts +66 -0
- package/dist/components/MarkdownRenderer/index.d.ts.map +1 -0
- package/dist/components/MarkdownRenderer/index.js +153 -0
- package/dist/components/MarkdownRenderer/index.js.map +1 -0
- package/dist/components/MessageInput/index.d.ts +85 -0
- package/dist/components/MessageInput/index.d.ts.map +1 -0
- package/dist/components/MessageInput/index.js +139 -0
- package/dist/components/MessageInput/index.js.map +1 -0
- package/dist/components/MessageList/index.d.ts +156 -0
- package/dist/components/MessageList/index.d.ts.map +1 -0
- package/dist/components/MessageList/index.js +352 -0
- package/dist/components/MessageList/index.js.map +1 -0
- package/dist/components/ReasoningSelector/index.d.ts +77 -0
- package/dist/components/ReasoningSelector/index.d.ts.map +1 -0
- package/dist/components/ReasoningSelector/index.js +156 -0
- package/dist/components/ReasoningSelector/index.js.map +1 -0
- package/dist/components/ReasoningView/index.d.ts +94 -0
- package/dist/components/ReasoningView/index.d.ts.map +1 -0
- package/dist/components/ReasoningView/index.js +83 -0
- package/dist/components/ReasoningView/index.js.map +1 -0
- package/dist/components/ScrollToPresentButton/index.d.ts +54 -0
- package/dist/components/ScrollToPresentButton/index.d.ts.map +1 -0
- package/dist/components/ScrollToPresentButton/index.js +44 -0
- package/dist/components/ScrollToPresentButton/index.js.map +1 -0
- package/dist/components/SessionsPanel/SessionTitleModal.d.ts +79 -0
- package/dist/components/SessionsPanel/SessionTitleModal.d.ts.map +1 -0
- package/dist/components/SessionsPanel/SessionTitleModal.js +102 -0
- package/dist/components/SessionsPanel/SessionTitleModal.js.map +1 -0
- package/dist/components/SessionsPanel/index.d.ts +100 -0
- package/dist/components/SessionsPanel/index.d.ts.map +1 -0
- package/dist/components/SessionsPanel/index.js +176 -0
- package/dist/components/SessionsPanel/index.js.map +1 -0
- package/dist/components/SystemNotice/index.d.ts +66 -0
- package/dist/components/SystemNotice/index.d.ts.map +1 -0
- package/dist/components/SystemNotice/index.js +81 -0
- package/dist/components/SystemNotice/index.js.map +1 -0
- package/dist/components/ThemeSwitcher/index.d.ts +66 -0
- package/dist/components/ThemeSwitcher/index.d.ts.map +1 -0
- package/dist/components/ThemeSwitcher/index.js +73 -0
- package/dist/components/ThemeSwitcher/index.js.map +1 -0
- package/dist/components/ToolCallView/formatContent.d.ts +36 -0
- package/dist/components/ToolCallView/formatContent.d.ts.map +1 -0
- package/dist/components/ToolCallView/formatContent.js +53 -0
- package/dist/components/ToolCallView/formatContent.js.map +1 -0
- package/dist/components/ToolCallView/index.d.ts +120 -0
- package/dist/components/ToolCallView/index.d.ts.map +1 -0
- package/dist/components/ToolCallView/index.js +104 -0
- package/dist/components/ToolCallView/index.js.map +1 -0
- package/dist/components/TpmBanner/index.d.ts +53 -0
- package/dist/components/TpmBanner/index.d.ts.map +1 -0
- package/dist/components/TpmBanner/index.js +116 -0
- package/dist/components/TpmBanner/index.js.map +1 -0
- package/dist/components/TurnStatusIndicator/index.d.ts +77 -0
- package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -0
- package/dist/components/TurnStatusIndicator/index.js +122 -0
- package/dist/components/TurnStatusIndicator/index.js.map +1 -0
- package/dist/components/internal/PasswordInput.d.ts +62 -0
- package/dist/components/internal/PasswordInput.d.ts.map +1 -0
- package/dist/components/internal/PasswordInput.js +42 -0
- package/dist/components/internal/PasswordInput.js.map +1 -0
- package/dist/context/AIHubContext.d.ts +220 -0
- package/dist/context/AIHubContext.d.ts.map +1 -0
- package/dist/context/AIHubContext.js +19 -0
- package/dist/context/AIHubContext.js.map +1 -0
- package/dist/context/AIHubProvider.d.ts +75 -0
- package/dist/context/AIHubProvider.d.ts.map +1 -0
- package/dist/context/AIHubProvider.js +954 -0
- package/dist/context/AIHubProvider.js.map +1 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +9 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/theme.d.ts +43 -0
- package/dist/context/theme.d.ts.map +1 -0
- package/dist/context/theme.js +70 -0
- package/dist/context/theme.js.map +1 -0
- package/dist/context/useAIHub.d.ts +104 -0
- package/dist/context/useAIHub.d.ts.map +1 -0
- package/dist/context/useAIHub.js +63 -0
- package/dist/context/useAIHub.js.map +1 -0
- package/dist/context/useAIHubAuth.d.ts +44 -0
- package/dist/context/useAIHubAuth.d.ts.map +1 -0
- package/dist/context/useAIHubAuth.js +35 -0
- package/dist/context/useAIHubAuth.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives/index.d.ts +99 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +97 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/state/affordances.d.ts +182 -0
- package/dist/state/affordances.d.ts.map +1 -0
- package/dist/state/affordances.js +189 -0
- package/dist/state/affordances.js.map +1 -0
- package/dist/state/stateReducer.d.ts +72 -0
- package/dist/state/stateReducer.d.ts.map +1 -0
- package/dist/state/stateReducer.js +122 -0
- package/dist/state/stateReducer.js.map +1 -0
- package/dist/stories/EffortControl.stories.d.ts +14 -0
- package/dist/stories/EffortControl.stories.d.ts.map +1 -0
- package/dist/stories/EffortControl.stories.js +43 -0
- package/dist/stories/EffortControl.stories.js.map +1 -0
- package/dist/stories/MarkdownRenderer.stories.d.ts +25 -0
- package/dist/stories/MarkdownRenderer.stories.d.ts.map +1 -0
- package/dist/stories/MarkdownRenderer.stories.js +178 -0
- package/dist/stories/MarkdownRenderer.stories.js.map +1 -0
- package/dist/stories/MessageBubble.stories.d.ts +21 -0
- package/dist/stories/MessageBubble.stories.d.ts.map +1 -0
- package/dist/stories/MessageBubble.stories.js +59 -0
- package/dist/stories/MessageBubble.stories.js.map +1 -0
- package/dist/stories/ReasoningView.stories.d.ts +18 -0
- package/dist/stories/ReasoningView.stories.d.ts.map +1 -0
- package/dist/stories/ReasoningView.stories.js +62 -0
- package/dist/stories/ReasoningView.stories.js.map +1 -0
- package/dist/stories/ScrollToPresentButton.stories.d.ts +18 -0
- package/dist/stories/ScrollToPresentButton.stories.d.ts.map +1 -0
- package/dist/stories/ScrollToPresentButton.stories.js +34 -0
- package/dist/stories/ScrollToPresentButton.stories.js.map +1 -0
- package/dist/stories/SystemNotice.stories.d.ts +17 -0
- package/dist/stories/SystemNotice.stories.d.ts.map +1 -0
- package/dist/stories/SystemNotice.stories.js +42 -0
- package/dist/stories/SystemNotice.stories.js.map +1 -0
- package/dist/stories/ThemeSwitcher.stories.d.ts +15 -0
- package/dist/stories/ThemeSwitcher.stories.d.ts.map +1 -0
- package/dist/stories/ThemeSwitcher.stories.js +35 -0
- package/dist/stories/ThemeSwitcher.stories.js.map +1 -0
- package/dist/stories/ToolCallView.stories.d.ts +20 -0
- package/dist/stories/ToolCallView.stories.d.ts.map +1 -0
- package/dist/stories/ToolCallView.stories.js +65 -0
- package/dist/stories/ToolCallView.stories.js.map +1 -0
- package/dist/stories/TpmBanner.stories.d.ts +24 -0
- package/dist/stories/TpmBanner.stories.d.ts.map +1 -0
- package/dist/stories/TpmBanner.stories.js +63 -0
- package/dist/stories/TpmBanner.stories.js.map +1 -0
- package/dist/stories/TurnStatusIndicator.stories.d.ts +25 -0
- package/dist/stories/TurnStatusIndicator.stories.d.ts.map +1 -0
- package/dist/stories/TurnStatusIndicator.stories.js +56 -0
- package/dist/stories/TurnStatusIndicator.stories.js.map +1 -0
- package/dist/stories/_mockProvider.d.ts +44 -0
- package/dist/stories/_mockProvider.d.ts.map +1 -0
- package/dist/stories/_mockProvider.js +84 -0
- package/dist/stories/_mockProvider.js.map +1 -0
- package/dist/types.d.ts +787 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/webcomponent/defineWebComponent.d.ts +106 -0
- package/dist/webcomponent/defineWebComponent.d.ts.map +1 -0
- package/dist/webcomponent/defineWebComponent.js +126 -0
- package/dist/webcomponent/defineWebComponent.js.map +1 -0
- package/dist/webcomponent/index.d.ts +7 -0
- package/dist/webcomponent/index.d.ts.map +1 -0
- package/dist/webcomponent/index.js +7 -0
- package/dist/webcomponent/index.js.map +1 -0
- package/package.json +63 -0
- package/src/__lint-canary__/step1-lint-canary.ts.disabled +28 -0
- package/src/auth/passwordPolicy.ts +75 -0
- package/src/auth/texts.ts +1294 -0
- package/src/client/AIHubClient.ts +1718 -0
- package/src/client/index.ts +30 -0
- package/src/client/sseParser.ts +168 -0
- package/src/client/tokenStorage.ts +131 -0
- package/src/components/AgentsPicker/index.tsx +147 -0
- package/src/components/AuthGate/index.tsx +132 -0
- package/src/components/CacheBadge/index.tsx +245 -0
- package/src/components/ChangePassword/index.tsx +375 -0
- package/src/components/ChatShell/ShellHeader.tsx +80 -0
- package/src/components/ChatShell/UserMenu.tsx +196 -0
- package/src/components/ChatShell/index.tsx +307 -0
- package/src/components/DataClubAIHub/index.tsx +300 -0
- package/src/components/DataClubAIHubAdmin/AgentEditorModal.tsx +505 -0
- package/src/components/DataClubAIHubAdmin/AgentsPage.tsx +342 -0
- package/src/components/DataClubAIHubAdmin/CreateUserModal.tsx +214 -0
- package/src/components/DataClubAIHubAdmin/PasswordRevealModal.tsx +204 -0
- package/src/components/DataClubAIHubAdmin/ResetPasswordModal.tsx +122 -0
- package/src/components/DataClubAIHubAdmin/RotatingMessageEditorModal.tsx +408 -0
- package/src/components/DataClubAIHubAdmin/RotatingMessagesPage.tsx +373 -0
- package/src/components/DataClubAIHubAdmin/SessionsOverviewPage.tsx +639 -0
- package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +979 -0
- package/src/components/DataClubAIHubAdmin/UsersPage.tsx +495 -0
- package/src/components/DataClubAIHubAdmin/index.tsx +277 -0
- package/src/components/DataClubAIHubLogin/index.tsx +282 -0
- package/src/components/EffortControl/index.tsx +138 -0
- package/src/components/Layout/index.tsx +92 -0
- package/src/components/MainColumn/index.tsx +131 -0
- package/src/components/MarkdownRenderer/index.tsx +256 -0
- package/src/components/MessageInput/index.tsx +283 -0
- package/src/components/MessageList/index.tsx +753 -0
- package/src/components/ReasoningSelector/index.tsx +268 -0
- package/src/components/ReasoningView/index.tsx +183 -0
- package/src/components/ScrollToPresentButton/index.tsx +85 -0
- package/src/components/SessionsPanel/SessionTitleModal.tsx +256 -0
- package/src/components/SessionsPanel/index.tsx +406 -0
- package/src/components/SystemNotice/index.tsx +151 -0
- package/src/components/ThemeSwitcher/index.tsx +136 -0
- package/src/components/ToolCallView/formatContent.ts +50 -0
- package/src/components/ToolCallView/index.tsx +274 -0
- package/src/components/TpmBanner/index.tsx +172 -0
- package/src/components/TurnStatusIndicator/index.tsx +203 -0
- package/src/components/internal/PasswordInput.tsx +145 -0
- package/src/context/AIHubContext.ts +249 -0
- package/src/context/AIHubProvider.tsx +1084 -0
- package/src/context/index.ts +9 -0
- package/src/context/theme.ts +76 -0
- package/src/context/useAIHub.ts +151 -0
- package/src/context/useAIHubAuth.ts +65 -0
- package/src/index.ts +275 -0
- package/src/primitives/index.ts +101 -0
- package/src/state/affordances.ts +321 -0
- package/src/state/stateReducer.ts +131 -0
- package/src/stories/EffortControl.stories.tsx +63 -0
- package/src/stories/MarkdownRenderer.stories.tsx +201 -0
- package/src/stories/MessageBubble.stories.tsx +101 -0
- package/src/stories/ReasoningView.stories.tsx +81 -0
- package/src/stories/ScrollToPresentButton.stories.tsx +52 -0
- package/src/stories/SystemNotice.stories.tsx +59 -0
- package/src/stories/ThemeSwitcher.stories.tsx +55 -0
- package/src/stories/ToolCallView.stories.tsx +95 -0
- package/src/stories/TpmBanner.stories.tsx +93 -0
- package/src/stories/TurnStatusIndicator.stories.tsx +86 -0
- package/src/stories/_mockProvider.tsx +116 -0
- package/src/styles/CLASSES.md +356 -0
- package/src/styles/default.css +2937 -0
- package/src/types.ts +867 -0
- package/src/webcomponent/defineWebComponent.tsx +193 -0
- 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
|
+
}
|