@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,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ReasoningSelector />` — capability-driven per-session reasoning UI.
|
|
3
|
+
*
|
|
4
|
+
* Renders a dynamic set of dropdowns based on the active session's
|
|
5
|
+
* `capabilities` (computed server-side from the agent's model handle):
|
|
6
|
+
*
|
|
7
|
+
* - **Anthropic / Bedrock** models → one "Effort" dropdown
|
|
8
|
+
* (`low | medium | high | xhigh | max`).
|
|
9
|
+
* - **OpenAI / OpenRouter** models → two independent dropdowns:
|
|
10
|
+
* "Reasoning" (`none | minimal | low | medium | high | xhigh`)
|
|
11
|
+
* and "Verbosity" (`low | medium | high`).
|
|
12
|
+
* - **Google AI / Vertex** models → numeric "Thinking budget" input.
|
|
13
|
+
* - **Models with no reasoning surface** (Z.AI, etc.) → renders nothing.
|
|
14
|
+
*
|
|
15
|
+
* Schema-driven: the dropdown enums come from the server's capability map,
|
|
16
|
+
* which in turn mirrors Letta's per-provider `*ModelSettings` shape. When
|
|
17
|
+
* Letta adds new values (e.g. a sixth effort tier), the server publishes
|
|
18
|
+
* them and the dropdown picks them up automatically — no UI changes needed.
|
|
19
|
+
*
|
|
20
|
+
* Wire flow: on change, calls `client.patchSessionReasoning(...)` which
|
|
21
|
+
* PATCHes the session row. The next `POST /chat` reads the new value and
|
|
22
|
+
* applies it to the agent's `model_settings` before opening the stream
|
|
23
|
+
* (see `chat.ts` pre-turn sync).
|
|
24
|
+
*
|
|
25
|
+
* Exported under `AIHub.ReasoningSelector`.
|
|
26
|
+
*
|
|
27
|
+
* @see SPECIFICATIONS.md §7.4 — reasoning effort surface
|
|
28
|
+
* @see packages/ai-hub-server/src/letta/modelSettings.ts — capability map
|
|
29
|
+
* + `buildModelSettings()` (the wire-shape construction)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { useEffect, useState, type ChangeEvent, type ReactNode } from "react";
|
|
33
|
+
|
|
34
|
+
import { useAIHub } from "../../context/useAIHub.js";
|
|
35
|
+
import type { Session } from "../../types.js";
|
|
36
|
+
|
|
37
|
+
/** BEM class names emitted by `<ReasoningSelector />`. */
|
|
38
|
+
export interface ReasoningSelectorClassNames {
|
|
39
|
+
/** The wrapper around the entire selector (may render 0..N child dropdowns). */
|
|
40
|
+
root: string;
|
|
41
|
+
/** Each individual `<label>` row. */
|
|
42
|
+
field: string;
|
|
43
|
+
/** Each label's text. */
|
|
44
|
+
label: string;
|
|
45
|
+
/** Each `<select>` element. */
|
|
46
|
+
select: string;
|
|
47
|
+
/** The numeric `<input>` (thinking budget). */
|
|
48
|
+
input: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_CLASS_NAMES: ReasoningSelectorClassNames = Object.freeze({
|
|
52
|
+
root: "data-club-ai-hub__reasoning-selector",
|
|
53
|
+
field: "data-club-ai-hub__reasoning-selector__field",
|
|
54
|
+
label: "data-club-ai-hub__reasoning-selector__label",
|
|
55
|
+
select: "data-club-ai-hub__reasoning-selector__select",
|
|
56
|
+
input: "data-club-ai-hub__reasoning-selector__input",
|
|
57
|
+
}) as ReasoningSelectorClassNames;
|
|
58
|
+
|
|
59
|
+
/** Props for `<ReasoningSelector />`. */
|
|
60
|
+
export interface ReasoningSelectorProps {
|
|
61
|
+
/** Disable all inputs (e.g. while a turn is streaming). Default false. */
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Optional label overrides. When omitted, sensible English defaults
|
|
65
|
+
* apply (`"Reasoning"`, `"Verbosity"`, `"Thinking budget"`).
|
|
66
|
+
*
|
|
67
|
+
* The reasoning dropdown uses one label (`reasoning`) regardless of
|
|
68
|
+
* whether the agent's model uses Anthropic-style `effort` or
|
|
69
|
+
* OpenAI-style `reasoning_effort` on the wire — from the user's
|
|
70
|
+
* perspective it's the same "how hard should the model think" knob.
|
|
71
|
+
*/
|
|
72
|
+
labels?: {
|
|
73
|
+
reasoning?: string;
|
|
74
|
+
verbosity?: string;
|
|
75
|
+
thinkingBudget?: string;
|
|
76
|
+
/** Empty-state placeholder option (e.g. "Default"). */
|
|
77
|
+
defaultPlaceholder?: string;
|
|
78
|
+
};
|
|
79
|
+
/** Extra class merged onto the root. */
|
|
80
|
+
className?: string;
|
|
81
|
+
/** Per-element class overrides. */
|
|
82
|
+
classNames?: Partial<ReasoningSelectorClassNames>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Capitalizes an enum value for the dropdown label — `"xhigh" → "X-High"`,
|
|
87
|
+
* `"max" → "Max"`. Keeps the wire enum honest and labels readable.
|
|
88
|
+
*/
|
|
89
|
+
function labelize(value: string): string {
|
|
90
|
+
if (value === "xhigh") return "Extra High";
|
|
91
|
+
if (value === "none") return "Off";
|
|
92
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Renders the capability-driven reasoning selector. Returns `null` when
|
|
97
|
+
* the active session's model has no reasoning surface.
|
|
98
|
+
*
|
|
99
|
+
* @param props - see `ReasoningSelectorProps`.
|
|
100
|
+
*/
|
|
101
|
+
export function ReasoningSelector(
|
|
102
|
+
props: ReasoningSelectorProps,
|
|
103
|
+
): ReactNode {
|
|
104
|
+
const { client, activeSessionId, state } = useAIHub();
|
|
105
|
+
const cls = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
|
|
106
|
+
const rootClass = props.className ? `${cls.root} ${props.className}` : cls.root;
|
|
107
|
+
|
|
108
|
+
// Per-component fetch: capabilities come from `GET /sessions/:id` (not
|
|
109
|
+
// the list endpoint). Re-fetched whenever the active session changes.
|
|
110
|
+
const [session, setSession] = useState<Session | null>(null);
|
|
111
|
+
const [saving, setSaving] = useState(false);
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
let cancelled = false;
|
|
115
|
+
if (!activeSessionId) {
|
|
116
|
+
setSession(null);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
void (async () => {
|
|
120
|
+
try {
|
|
121
|
+
const s = await client.getSession(activeSessionId);
|
|
122
|
+
if (!cancelled) setSession(s);
|
|
123
|
+
} catch {
|
|
124
|
+
if (!cancelled) setSession(null);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
return () => {
|
|
128
|
+
cancelled = true;
|
|
129
|
+
};
|
|
130
|
+
}, [activeSessionId, client]);
|
|
131
|
+
|
|
132
|
+
if (!session) return null;
|
|
133
|
+
const caps = session.capabilities;
|
|
134
|
+
if (!caps) return null;
|
|
135
|
+
|
|
136
|
+
const hasAnyControl =
|
|
137
|
+
caps.effort !== undefined ||
|
|
138
|
+
caps.reasoning_effort !== undefined ||
|
|
139
|
+
caps.verbosity !== undefined ||
|
|
140
|
+
caps.thinking_budget !== undefined;
|
|
141
|
+
if (!hasAnyControl) return null;
|
|
142
|
+
|
|
143
|
+
const labels = {
|
|
144
|
+
reasoning: props.labels?.reasoning ?? "Reasoning",
|
|
145
|
+
verbosity: props.labels?.verbosity ?? "Verbosity",
|
|
146
|
+
thinkingBudget: props.labels?.thinkingBudget ?? "Thinking budget",
|
|
147
|
+
defaultPlaceholder: props.labels?.defaultPlaceholder ?? "Default",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Disabled when caller says so OR when a turn is streaming — keeps the
|
|
151
|
+
// user from changing settings mid-turn (the PATCH would race the
|
|
152
|
+
// pre-turn sync).
|
|
153
|
+
const disabled = (props.disabled ?? false) || state.status === "running";
|
|
154
|
+
|
|
155
|
+
async function persistPatch(
|
|
156
|
+
field: "effort" | "verbosity" | "thinkingBudget",
|
|
157
|
+
value: string | number | null,
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
if (!activeSessionId) return;
|
|
160
|
+
setSaving(true);
|
|
161
|
+
try {
|
|
162
|
+
const patch: Parameters<typeof client.patchSessionReasoning>[1] = {};
|
|
163
|
+
if (field === "effort") patch.effort = value as string | null;
|
|
164
|
+
else if (field === "verbosity") patch.verbosity = value as string | null;
|
|
165
|
+
else patch.thinkingBudget = value as number | null;
|
|
166
|
+
const updated = await client.patchSessionReasoning(activeSessionId, patch);
|
|
167
|
+
setSession(updated);
|
|
168
|
+
} catch {
|
|
169
|
+
// Soft-fail: leave the existing state. A toast / banner could be
|
|
170
|
+
// added later; matching pattern with patchSession (which also
|
|
171
|
+
// doesn't surface errors to users).
|
|
172
|
+
} finally {
|
|
173
|
+
setSaving(false);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleEffortChange(e: ChangeEvent<HTMLSelectElement>): void {
|
|
178
|
+
const v = e.target.value;
|
|
179
|
+
void persistPatch("effort", v === "" ? null : v);
|
|
180
|
+
}
|
|
181
|
+
function handleVerbosityChange(e: ChangeEvent<HTMLSelectElement>): void {
|
|
182
|
+
const v = e.target.value;
|
|
183
|
+
void persistPatch("verbosity", v === "" ? null : v);
|
|
184
|
+
}
|
|
185
|
+
function handleBudgetChange(e: ChangeEvent<HTMLInputElement>): void {
|
|
186
|
+
const v = e.target.value;
|
|
187
|
+
void persistPatch("thinkingBudget", v === "" ? null : Number(v));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const currentEffort = session.reasoning?.effort ?? "";
|
|
191
|
+
const currentVerbosity = session.reasoning?.verbosity ?? "";
|
|
192
|
+
const currentBudget = session.reasoning?.thinking_budget ?? "";
|
|
193
|
+
|
|
194
|
+
// Anthropic uses `effort`, OpenAI uses `reasoning_effort` — same control
|
|
195
|
+
// shape (dropdown), different field on the wire. Server stores either
|
|
196
|
+
// in `session.reasoning.effort` (we don't split into two columns —
|
|
197
|
+
// it's contextual to the agent's model). The UI label is unified —
|
|
198
|
+
// both render as "Reasoning" so the user sees one consistent control.
|
|
199
|
+
const effortCap = caps.effort ?? caps.reasoning_effort;
|
|
200
|
+
const effortLabel = labels.reasoning;
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
className={rootClass}
|
|
205
|
+
data-testid="reasoning-selector"
|
|
206
|
+
data-saving={saving ? "true" : undefined}
|
|
207
|
+
>
|
|
208
|
+
{effortCap !== undefined && (
|
|
209
|
+
<label className={cls.field}>
|
|
210
|
+
<span className={cls.label}>{effortLabel}</span>
|
|
211
|
+
<select
|
|
212
|
+
className={cls.select}
|
|
213
|
+
value={currentEffort}
|
|
214
|
+
onChange={handleEffortChange}
|
|
215
|
+
disabled={disabled || saving}
|
|
216
|
+
data-testid="reasoning-selector-effort"
|
|
217
|
+
aria-label={effortLabel}
|
|
218
|
+
>
|
|
219
|
+
<option value="">{labels.defaultPlaceholder}</option>
|
|
220
|
+
{effortCap.enum.map((v) => (
|
|
221
|
+
<option key={v} value={v}>
|
|
222
|
+
{labelize(v)}
|
|
223
|
+
</option>
|
|
224
|
+
))}
|
|
225
|
+
</select>
|
|
226
|
+
</label>
|
|
227
|
+
)}
|
|
228
|
+
{caps.verbosity !== undefined && (
|
|
229
|
+
<label className={cls.field}>
|
|
230
|
+
<span className={cls.label}>{labels.verbosity}</span>
|
|
231
|
+
<select
|
|
232
|
+
className={cls.select}
|
|
233
|
+
value={currentVerbosity}
|
|
234
|
+
onChange={handleVerbosityChange}
|
|
235
|
+
disabled={disabled || saving}
|
|
236
|
+
data-testid="reasoning-selector-verbosity"
|
|
237
|
+
aria-label={labels.verbosity}
|
|
238
|
+
>
|
|
239
|
+
<option value="">{labels.defaultPlaceholder}</option>
|
|
240
|
+
{caps.verbosity.enum.map((v) => (
|
|
241
|
+
<option key={v} value={v}>
|
|
242
|
+
{labelize(v)}
|
|
243
|
+
</option>
|
|
244
|
+
))}
|
|
245
|
+
</select>
|
|
246
|
+
</label>
|
|
247
|
+
)}
|
|
248
|
+
{caps.thinking_budget !== undefined && (
|
|
249
|
+
<label className={cls.field}>
|
|
250
|
+
<span className={cls.label}>{labels.thinkingBudget}</span>
|
|
251
|
+
<input
|
|
252
|
+
type="number"
|
|
253
|
+
className={cls.input}
|
|
254
|
+
value={currentBudget}
|
|
255
|
+
min={caps.thinking_budget.min}
|
|
256
|
+
max={caps.thinking_budget.max}
|
|
257
|
+
step={256}
|
|
258
|
+
onChange={handleBudgetChange}
|
|
259
|
+
disabled={disabled || saving}
|
|
260
|
+
data-testid="reasoning-selector-budget"
|
|
261
|
+
aria-label={labels.thinkingBudget}
|
|
262
|
+
placeholder={labels.defaultPlaceholder}
|
|
263
|
+
/>
|
|
264
|
+
</label>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ReasoningView />` — collapsible reasoning block inside an assistant
|
|
3
|
+
* bubble.
|
|
4
|
+
*
|
|
5
|
+
* Default-collapsed (the spec calls this out — reasoning is for "I want
|
|
6
|
+
* to inspect this" UX, not "I want to read it inline by default").
|
|
7
|
+
* Header shows the duration when known; body is rendered in monospace.
|
|
8
|
+
*
|
|
9
|
+
* Step 10 only renders reasoning when:
|
|
10
|
+
*
|
|
11
|
+
* - the turn was sent with a non-`none` effort (i.e. the assistant
|
|
12
|
+
* actually produced a reasoning block), AND
|
|
13
|
+
* - `reasoningVisible` (resolved) is true.
|
|
14
|
+
*
|
|
15
|
+
* The decision about whether reasoning is *present* lives upstream in
|
|
16
|
+
* `MessageList` — this primitive just renders whatever block content +
|
|
17
|
+
* timing it's given.
|
|
18
|
+
*
|
|
19
|
+
* Exported under `AIHub.ReasoningView` (re-exported via the namespace).
|
|
20
|
+
*
|
|
21
|
+
* @see SPECIFICATIONS.md §7.3 — showReasoning prop
|
|
22
|
+
* @see ui-sketches/03-chat.md — reasoning_block --collapsed / --expanded
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useState, type ReactNode } from "react";
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
resolveAIHubTexts,
|
|
29
|
+
type AIHubTexts,
|
|
30
|
+
type DeepPartial,
|
|
31
|
+
} from "../../auth/texts.js";
|
|
32
|
+
|
|
33
|
+
/** BEM class names emitted by `<ReasoningView />`. */
|
|
34
|
+
export interface ReasoningViewClassNames {
|
|
35
|
+
/** Outer block. */
|
|
36
|
+
root: string;
|
|
37
|
+
/** Modifier — `--collapsed`. */
|
|
38
|
+
rootCollapsed: string;
|
|
39
|
+
/** Modifier — `--expanded`. */
|
|
40
|
+
rootExpanded: string;
|
|
41
|
+
/** Header element (always present). */
|
|
42
|
+
header: string;
|
|
43
|
+
/** Title text inside the header. */
|
|
44
|
+
title: string;
|
|
45
|
+
/** Expand/collapse toggle button. */
|
|
46
|
+
toggle: string;
|
|
47
|
+
/** Body element (only mounted when expanded). */
|
|
48
|
+
body: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_CLASS_NAMES: ReasoningViewClassNames = Object.freeze({
|
|
52
|
+
root: "data-club-ai-hub__reasoning-block",
|
|
53
|
+
rootCollapsed: "data-club-ai-hub__reasoning-block--collapsed",
|
|
54
|
+
rootExpanded: "data-club-ai-hub__reasoning-block--expanded",
|
|
55
|
+
header: "data-club-ai-hub__reasoning-block__header",
|
|
56
|
+
title: "data-club-ai-hub__reasoning-block__title",
|
|
57
|
+
toggle: "data-club-ai-hub__reasoning-block__toggle",
|
|
58
|
+
body: "data-club-ai-hub__reasoning-block__body",
|
|
59
|
+
}) as ReasoningViewClassNames;
|
|
60
|
+
|
|
61
|
+
/** Props for `<ReasoningView />`. */
|
|
62
|
+
export interface ReasoningViewProps {
|
|
63
|
+
/** The reasoning text (possibly empty while deltas are still arriving). */
|
|
64
|
+
content: string;
|
|
65
|
+
/** Wall-clock duration in seconds, when known. */
|
|
66
|
+
durationSeconds?: number;
|
|
67
|
+
/**
|
|
68
|
+
* When true, the block is still streaming — placeholder text is
|
|
69
|
+
* shown instead of an empty body and the duration is omitted.
|
|
70
|
+
*/
|
|
71
|
+
streaming?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Override the initial expanded state. Default false (collapsed). When
|
|
74
|
+
* `streaming` is true the initial state is also false — the user opts
|
|
75
|
+
* in to watch the reasoning live.
|
|
76
|
+
*
|
|
77
|
+
* Ignored when `expanded` is passed (controlled mode).
|
|
78
|
+
*/
|
|
79
|
+
defaultExpanded?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Controlled expanded state. When set, `<ReasoningView />` defers all
|
|
82
|
+
* expand/collapse decisions to the parent — `onToggle` fires on toggle
|
|
83
|
+
* clicks and the parent decides whether the next render should pass
|
|
84
|
+
* `true` or `false`. Used by `<MessageList />` to enforce the
|
|
85
|
+
* accordion + auto-expand-while-streaming rules across all blocks in
|
|
86
|
+
* the conversation. When omitted, the component falls back to internal
|
|
87
|
+
* `useState` for backward compatibility (the original uncontrolled
|
|
88
|
+
* behavior, default-collapsed with a per-instance toggle).
|
|
89
|
+
*/
|
|
90
|
+
expanded?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Toggle handler for the controlled mode. Fires when the user clicks
|
|
93
|
+
* the expand/collapse button. The parent inspects its own accordion
|
|
94
|
+
* state to compute the next `expanded` prop. Ignored in uncontrolled
|
|
95
|
+
* mode (the internal `useState` toggles itself).
|
|
96
|
+
*/
|
|
97
|
+
onToggle?: () => void;
|
|
98
|
+
/** Wrapper-supplied i18n. */
|
|
99
|
+
texts?: DeepPartial<AIHubTexts>;
|
|
100
|
+
/** Extra class merged onto the root. */
|
|
101
|
+
className?: string;
|
|
102
|
+
/** Per-element class overrides. */
|
|
103
|
+
classNames?: Partial<ReasoningViewClassNames>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Renders the reasoning block.
|
|
108
|
+
*
|
|
109
|
+
* @param props - see `ReasoningViewProps`.
|
|
110
|
+
* @returns the rendered block.
|
|
111
|
+
*/
|
|
112
|
+
export function ReasoningView(props: ReasoningViewProps): ReactNode {
|
|
113
|
+
const texts = resolveAIHubTexts(props.texts);
|
|
114
|
+
const cls = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
|
|
115
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState(
|
|
116
|
+
props.defaultExpanded ?? false,
|
|
117
|
+
);
|
|
118
|
+
const isControlled = props.expanded !== undefined;
|
|
119
|
+
const expanded = isControlled ? (props.expanded as boolean) : uncontrolledExpanded;
|
|
120
|
+
const handleToggle = isControlled
|
|
121
|
+
? props.onToggle ?? (() => {})
|
|
122
|
+
: () => setUncontrolledExpanded((e) => !e);
|
|
123
|
+
|
|
124
|
+
const seconds = props.durationSeconds ?? 0;
|
|
125
|
+
// Pick the WITH-duration template only when we actually have a
|
|
126
|
+
// duration to show. Historical bubbles (loaded via `getSessionMessages`)
|
|
127
|
+
// don't carry a duration today, so they render the no-duration variant
|
|
128
|
+
// ("🧠Reasoning") rather than the awkward "🧠Reasoning (…s)" we'd
|
|
129
|
+
// get from running the placeholder substitution against zero.
|
|
130
|
+
const hasDuration = seconds > 0;
|
|
131
|
+
let headerText: string;
|
|
132
|
+
if (hasDuration) {
|
|
133
|
+
const template = expanded
|
|
134
|
+
? texts.chat.reasoningExpandedHeader
|
|
135
|
+
: texts.chat.reasoningCollapsedHeader;
|
|
136
|
+
headerText = template.replace("{seconds}", seconds.toFixed(1));
|
|
137
|
+
} else {
|
|
138
|
+
headerText = expanded
|
|
139
|
+
? texts.chat.reasoningExpandedHeaderNoDuration
|
|
140
|
+
: texts.chat.reasoningCollapsedHeaderNoDuration;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rootClass = [
|
|
144
|
+
cls.root,
|
|
145
|
+
expanded ? cls.rootExpanded : cls.rootCollapsed,
|
|
146
|
+
props.className,
|
|
147
|
+
]
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join(" ");
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
className={rootClass}
|
|
154
|
+
data-testid="reasoning-view"
|
|
155
|
+
data-expanded={expanded ? "true" : "false"}
|
|
156
|
+
>
|
|
157
|
+
<div className={cls.header}>
|
|
158
|
+
<span className={cls.title}>{headerText}</span>
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
className={cls.toggle}
|
|
162
|
+
onClick={handleToggle}
|
|
163
|
+
aria-expanded={expanded}
|
|
164
|
+
aria-label={
|
|
165
|
+
expanded
|
|
166
|
+
? texts.chat.reasoningCollapseAriaLabel
|
|
167
|
+
: texts.chat.reasoningExpandAriaLabel
|
|
168
|
+
}
|
|
169
|
+
data-testid="reasoning-view-toggle"
|
|
170
|
+
>
|
|
171
|
+
{expanded ? "â–¼" : "â–¶"}
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
{expanded ? (
|
|
175
|
+
<div className={cls.body} data-testid="reasoning-view-body">
|
|
176
|
+
{props.streaming && props.content.trim().length === 0
|
|
177
|
+
? texts.chat.reasoningThinkingPlaceholder
|
|
178
|
+
: props.content}
|
|
179
|
+
</div>
|
|
180
|
+
) : null}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<ScrollToPresentButton />` — floating "↓ Jump to latest" button that
|
|
3
|
+
* appears when the user has scrolled away from the bottom of the
|
|
4
|
+
* message list during a running turn.
|
|
5
|
+
*
|
|
6
|
+
* The §10.4 predicate is `status === "running" && userScrolledAwayFromBottom`.
|
|
7
|
+
* The `status` half is computed via `chatAffordances`; the
|
|
8
|
+
* `userScrolledAwayFromBottom` half is local to the message list. This
|
|
9
|
+
* primitive accepts both as props so the predicate logic stays out of
|
|
10
|
+
* the primitive (which would otherwise need to reach into a scroll
|
|
11
|
+
* ref it doesn't own).
|
|
12
|
+
*
|
|
13
|
+
* Rendering also requires `scrollToPresentVisible` (server toggle ∧
|
|
14
|
+
* React prop) — the parent does that AND-merge upstream before
|
|
15
|
+
* passing `visible` here.
|
|
16
|
+
*
|
|
17
|
+
* Exported under `AIHub.ScrollToPresentButton`.
|
|
18
|
+
*
|
|
19
|
+
* @see SPECIFICATIONS.md §10.4 — scroll-to-present predicate
|
|
20
|
+
* @see ui-sketches/03-chat.md — scroll_to_present_button
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { type ReactNode } from "react";
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
resolveAIHubTexts,
|
|
27
|
+
type AIHubTexts,
|
|
28
|
+
type DeepPartial,
|
|
29
|
+
} from "../../auth/texts.js";
|
|
30
|
+
|
|
31
|
+
/** BEM class names emitted by `<ScrollToPresentButton />`. */
|
|
32
|
+
export interface ScrollToPresentButtonClassNames {
|
|
33
|
+
/** Outer button element. */
|
|
34
|
+
root: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_CLASS_NAMES: ScrollToPresentButtonClassNames = Object.freeze({
|
|
38
|
+
root: "data-club-ai-hub__scroll-to-present-button",
|
|
39
|
+
}) as ScrollToPresentButtonClassNames;
|
|
40
|
+
|
|
41
|
+
/** Props for `<ScrollToPresentButton />`. */
|
|
42
|
+
export interface ScrollToPresentButtonProps {
|
|
43
|
+
/**
|
|
44
|
+
* Whether to render. Caller computes the predicate via
|
|
45
|
+
* `chatAffordances(state).scrollToPresentVisible &&
|
|
46
|
+
* userScrolledAwayFromBottom && scrollToPresentVisibleFlag`.
|
|
47
|
+
* Passing `false` returns `null`.
|
|
48
|
+
*/
|
|
49
|
+
visible: boolean;
|
|
50
|
+
/** Click handler — typically scrolls the message list container. */
|
|
51
|
+
onClick: () => void;
|
|
52
|
+
/** Wrapper-supplied i18n. */
|
|
53
|
+
texts?: DeepPartial<AIHubTexts>;
|
|
54
|
+
/** Extra class merged onto the root. */
|
|
55
|
+
className?: string;
|
|
56
|
+
/** Per-element class overrides. */
|
|
57
|
+
classNames?: Partial<ScrollToPresentButtonClassNames>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Renders the button.
|
|
62
|
+
*
|
|
63
|
+
* @param props - see `ScrollToPresentButtonProps`.
|
|
64
|
+
* @returns the rendered button, or `null` when hidden.
|
|
65
|
+
*/
|
|
66
|
+
export function ScrollToPresentButton(
|
|
67
|
+
props: ScrollToPresentButtonProps,
|
|
68
|
+
): ReactNode {
|
|
69
|
+
if (!props.visible) return null;
|
|
70
|
+
const texts = resolveAIHubTexts(props.texts);
|
|
71
|
+
const cls = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
|
|
72
|
+
const rootClass = props.className
|
|
73
|
+
? `${cls.root} ${props.className}`
|
|
74
|
+
: cls.root;
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={rootClass}
|
|
79
|
+
onClick={props.onClick}
|
|
80
|
+
data-testid="scroll-to-present-button"
|
|
81
|
+
>
|
|
82
|
+
{texts.chat.scrollToPresentButton}
|
|
83
|
+
</button>
|
|
84
|
+
);
|
|
85
|
+
}
|