@nextclaw/ui 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
  3. package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
  4. package/dist/assets/chat-page-CEdavJXc.js +105 -0
  5. package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
  10. package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
  11. package/dist/assets/doc-browser-BJRLhztv.js +1 -0
  12. package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
  15. package/dist/assets/index-BDN1zzMQ.js +105 -0
  16. package/dist/assets/index-g3uSZXoc.css +1 -0
  17. package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-D6hYXROd.js +1 -0
  21. package/dist/assets/plus-DPjb0piO.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
  23. package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
  25. package/dist/assets/remote-B0G80uPr.js +1 -0
  26. package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
  27. package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
  34. package/dist/assets/use-config-DmT0o1gv.js +1 -0
  35. package/dist/assets/x-BiSSpgqB.js +1 -0
  36. package/dist/index.html +15 -14
  37. package/package.json +9 -9
  38. package/src/app/components/app-presenter-provider.tsx +11 -2
  39. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  40. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  41. package/src/app/components/layout/app-layout.tsx +18 -4
  42. package/src/app/components/layout/sidebar-items.tsx +194 -71
  43. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  44. package/src/app/components/layout/sidebar.tsx +340 -83
  45. package/src/app/managers/viewport-layout.manager.ts +10 -0
  46. package/src/app/stores/viewport-layout.store.ts +39 -3
  47. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  48. package/src/features/agents/components/agents-page.tsx +10 -6
  49. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  50. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  51. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  52. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  53. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
  54. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
  55. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  56. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  57. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
  58. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  59. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
  60. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
  61. package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
  62. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  63. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
  64. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
  65. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
  66. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  67. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
  68. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  69. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  70. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  71. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  72. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  73. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  74. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  75. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  76. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  77. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  78. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  79. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  80. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  81. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  82. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  83. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  84. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  85. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  86. package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
  87. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  88. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  89. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  90. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
  91. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  92. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  93. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  94. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  95. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  96. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  97. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  98. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  99. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  100. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
  101. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  102. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
  103. package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
  104. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  105. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  106. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  107. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  108. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  109. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  110. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  111. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  112. package/src/features/chat/index.ts +0 -1
  113. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  114. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  115. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  116. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  117. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  118. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  119. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  120. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  121. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  122. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  123. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  124. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  125. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  126. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  127. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  128. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  129. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  130. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  131. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  132. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  133. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  134. package/src/shared/lib/ui-document-title/index.ts +50 -21
  135. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  136. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  137. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  138. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  139. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  140. package/dist/assets/index-7o1WdUbU.js +0 -103
  141. package/dist/assets/index-DQWmEWzk.css +0 -1
  142. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  143. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  144. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  145. package/dist/assets/plus-Bnt56oqG.js +0 -1
  146. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  147. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  148. package/dist/assets/remote-B0LMs8u7.js +0 -1
  149. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  150. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  151. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  152. package/dist/assets/x-CW2KO-Hz.js +0 -1
  153. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  154. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  155. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  156. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  157. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  158. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  159. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  160. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  161. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  162. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  163. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  164. package/src/features/chat/stores/chat-input.store.ts +0 -104
  165. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -0,0 +1,247 @@
1
+ import { BrandHeader } from "@/shared/components/common/brand-header";
2
+ import { StatusBadge } from "@/shared/components/common/status-badge";
3
+ import { ChatSidebarListModeSwitch } from "@/features/chat/components/chat-sidebar-list-mode-switch";
4
+ import type {
5
+ groupSessionsByDate,
6
+ groupSessionsByProject,
7
+ } from "@/features/chat/features/session/utils/chat-sidebar-session-groups.utils";
8
+ import type { NcpSessionListItemView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
9
+ import type { useSystemStatus } from "@/features/system-status";
10
+ import { cn } from "@/shared/lib/utils";
11
+ import { t, type I18nLanguage } from "@/shared/lib/i18n";
12
+ import type { UiTheme } from "@/shared/lib/theme";
13
+ import { SidebarNavLinkItem } from "@/app/components/layout/sidebar-items";
14
+ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
15
+ import {
16
+ AlarmClock,
17
+ Bot,
18
+ BrainCircuit,
19
+ PanelLeftClose,
20
+ PanelLeftOpen,
21
+ } from "lucide-react";
22
+ import { ChatSidebarSessionList } from "@/features/chat/features/session/components/chat-sidebar-session-list";
23
+ import { ChatSidebarUtilityMenu } from "@/features/chat/components/layout/chat-sidebar-utility-menu";
24
+ import { isWindowsDesktopHost } from "@/platforms/desktop";
25
+ import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
26
+ import {
27
+ SIDEBAR_RAIL_CONTROL_CLASS,
28
+ SIDEBAR_RAIL_ICON_CLASS,
29
+ SIDEBAR_RAIL_PADDING_X_CLASS,
30
+ SIDEBAR_RAIL_STACK_CLASS,
31
+ SIDEBAR_RAIL_SURFACE_CLASS,
32
+ } from "@/app/components/layout/sidebar-rail.styles";
33
+
34
+ const navItems = [
35
+ {
36
+ target: "/cron",
37
+ label: () => t("chatSidebarScheduledTasks"),
38
+ icon: AlarmClock,
39
+ },
40
+ {
41
+ target: "/skills",
42
+ label: () => t("chatSidebarSkills"),
43
+ icon: BrainCircuit,
44
+ },
45
+ { target: "/agents", label: () => t("agentsPageTitle"), icon: Bot },
46
+ ];
47
+
48
+ function ChatSidebarCollapseButton({ isCollapsed }: { isCollapsed: boolean }) {
49
+ const label = isCollapsed ? t("sidebarExpand") : t("sidebarCollapse");
50
+ const Icon = isCollapsed ? PanelLeftOpen : PanelLeftClose;
51
+
52
+ return (
53
+ <IconActionButton
54
+ icon={<Icon className={SIDEBAR_RAIL_ICON_CLASS} />}
55
+ label={label}
56
+ className={cn(SIDEBAR_RAIL_SURFACE_CLASS, isCollapsed && SIDEBAR_RAIL_CONTROL_CLASS)}
57
+ onClick={viewportLayoutManager.toggleSidebarCollapsed}
58
+ />
59
+ );
60
+ }
61
+
62
+ export function ChatSidebarDesktopHeader({
63
+ connectionStatus,
64
+ isCollapsed,
65
+ }: {
66
+ connectionStatus: ReturnType<typeof useSystemStatus>["connectionStatus"];
67
+ isCollapsed: boolean;
68
+ }) {
69
+ const isWindowsHost = isWindowsDesktopHost();
70
+ const shouldReserveMacWindowControls =
71
+ typeof window !== "undefined" &&
72
+ window.nextclawDesktop?.platform === "darwin";
73
+
74
+ return (
75
+ <div
76
+ className={cn(
77
+ "flex items-center",
78
+ isCollapsed
79
+ ? "justify-center px-2 py-1.5"
80
+ : isWindowsHost
81
+ ? "justify-end px-3 py-1.5"
82
+ : "gap-2 px-5 py-2",
83
+ isCollapsed && shouldReserveMacWindowControls ? "pt-8" : null,
84
+ )}
85
+ >
86
+ {isCollapsed || isWindowsHost ? null : (
87
+ <BrandHeader
88
+ className="flex min-w-0 flex-1 items-center gap-2"
89
+ suffix={<StatusBadge status={connectionStatus} />}
90
+ />
91
+ )}
92
+ <ChatSidebarCollapseButton isCollapsed={isCollapsed} />
93
+ </div>
94
+ );
95
+ }
96
+
97
+ export function ChatSidebarDesktopNav({
98
+ isCollapsed,
99
+ }: {
100
+ isCollapsed: boolean;
101
+ }) {
102
+ return (
103
+ <>
104
+ <div className={cn("pb-2", isCollapsed ? "px-0" : "px-3")}>
105
+ <ul className={isCollapsed ? SIDEBAR_RAIL_STACK_CLASS : "space-y-0.5"}>
106
+ {navItems.map((item) => (
107
+ <li
108
+ key={item.target}
109
+ className={isCollapsed ? "flex justify-center" : undefined}
110
+ >
111
+ <SidebarNavLinkItem
112
+ to={item.target}
113
+ label={item.label()}
114
+ icon={item.icon}
115
+ density="compact"
116
+ collapsed={isCollapsed}
117
+ />
118
+ </li>
119
+ ))}
120
+ </ul>
121
+ </div>
122
+ <div
123
+ className={cn(
124
+ "border-t border-gray-200/60",
125
+ isCollapsed ? "mx-2 my-1.5" : "mx-4",
126
+ )}
127
+ />
128
+ </>
129
+ );
130
+ }
131
+
132
+ export function ChatSidebarSessionArea({
133
+ defaultSessionType,
134
+ groups,
135
+ isCollapsed,
136
+ isLoading,
137
+ isProjectFirstView,
138
+ onCreateSession,
139
+ onSelectMode,
140
+ projectGroups,
141
+ renderSessionItem,
142
+ sessionTypeOptions,
143
+ }: {
144
+ defaultSessionType: string;
145
+ groups: ReturnType<typeof groupSessionsByDate>;
146
+ isCollapsed: boolean;
147
+ isLoading: boolean;
148
+ isProjectFirstView: boolean;
149
+ onCreateSession: (sessionType: string, projectRoot?: string | null) => void;
150
+ onSelectMode: (mode: "time-first" | "project-first") => void;
151
+ projectGroups: ReturnType<typeof groupSessionsByProject>;
152
+ renderSessionItem: (item: NcpSessionListItemView) => JSX.Element;
153
+ sessionTypeOptions: Parameters<
154
+ typeof ChatSidebarSessionList
155
+ >[0]["sessionTypeOptions"];
156
+ }) {
157
+ if (isCollapsed) {
158
+ return <div className="min-h-0 flex-1" aria-hidden="true" />;
159
+ }
160
+
161
+ return (
162
+ <>
163
+ <div className="flex items-center justify-between px-5 pb-2 pt-3">
164
+ <div className="text-[11px] font-medium uppercase tracking-wider text-gray-400">
165
+ {t("chatSidebarTaskRecords")}
166
+ </div>
167
+ <ChatSidebarListModeSwitch
168
+ isProjectFirstView={isProjectFirstView}
169
+ onSelectMode={onSelectMode}
170
+ />
171
+ </div>
172
+
173
+ <div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar px-3 py-2">
174
+ <ChatSidebarSessionList
175
+ isLoading={isLoading}
176
+ isProjectFirstView={isProjectFirstView}
177
+ groups={groups}
178
+ projectGroups={projectGroups}
179
+ defaultSessionType={defaultSessionType}
180
+ sessionTypeOptions={sessionTypeOptions}
181
+ renderSessionItem={renderSessionItem}
182
+ onCreateSession={onCreateSession}
183
+ />
184
+ </div>
185
+ </>
186
+ );
187
+ }
188
+
189
+ export function ChatSidebarDesktopFooter({
190
+ currentLanguage,
191
+ currentLanguageLabel,
192
+ currentTheme,
193
+ currentThemeLabel,
194
+ isCollapsed,
195
+ isOpen,
196
+ languageOptions,
197
+ onOpenApps,
198
+ onOpenChange,
199
+ onOpenDocs,
200
+ onSelectLanguage,
201
+ onSelectTheme,
202
+ themeOptions,
203
+ }: {
204
+ currentLanguage: I18nLanguage;
205
+ currentLanguageLabel: string;
206
+ currentTheme: UiTheme;
207
+ currentThemeLabel: string;
208
+ isCollapsed: boolean;
209
+ isOpen: boolean;
210
+ languageOptions: Array<{ value: I18nLanguage; label: string }>;
211
+ onOpenApps: () => void;
212
+ onOpenChange: (open: boolean) => void;
213
+ onOpenDocs: () => void;
214
+ onSelectLanguage: (language: I18nLanguage) => void;
215
+ onSelectTheme: (theme: UiTheme) => void;
216
+ themeOptions: Array<{
217
+ value: UiTheme;
218
+ label: string;
219
+ }>;
220
+ }) {
221
+ return (
222
+ <div
223
+ className={cn(
224
+ "border-t border-gray-200/60 py-3",
225
+ isCollapsed
226
+ ? cn("flex justify-center", SIDEBAR_RAIL_PADDING_X_CLASS)
227
+ : "px-3",
228
+ )}
229
+ >
230
+ <ChatSidebarUtilityMenu
231
+ isOpen={isOpen}
232
+ onOpenChange={onOpenChange}
233
+ currentTheme={currentTheme}
234
+ currentThemeLabel={currentThemeLabel}
235
+ themeOptions={themeOptions}
236
+ onSelectTheme={onSelectTheme}
237
+ currentLanguage={currentLanguage}
238
+ currentLanguageLabel={currentLanguageLabel}
239
+ languageOptions={languageOptions}
240
+ onSelectLanguage={onSelectLanguage}
241
+ onOpenDocs={onOpenDocs}
242
+ onOpenApps={onOpenApps}
243
+ collapsed={isCollapsed}
244
+ />
245
+ </div>
246
+ );
247
+ }
@@ -8,12 +8,19 @@ import {
8
8
  } from "@/shared/components/ui/popover";
9
9
  import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
10
10
  import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
11
- import type { ChatInputSnapshot } from "@/features/chat/stores/chat-input.store";
11
+ import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
12
12
  import { cn } from "@/shared/lib/utils";
13
13
  import { t } from "@/shared/lib/i18n";
14
14
  import { Bot, ChevronDown, Plus, Search } from "lucide-react";
15
+ import {
16
+ SIDEBAR_RAIL_CONTROL_CLASS,
17
+ SIDEBAR_RAIL_ICON_CLASS,
18
+ SIDEBAR_RAIL_ITEM_GAP_CLASS,
19
+ SIDEBAR_RAIL_PRIMARY_SURFACE_CLASS,
20
+ SIDEBAR_RAIL_SURFACE_CLASS,
21
+ } from "@/app/components/layout/sidebar-rail.styles";
15
22
 
16
- type SessionTypeOption = ChatInputSnapshot["sessionTypeOptions"][number];
23
+ type SessionTypeOption = ChatSessionTypeOption;
17
24
  type NewSessionActionStyleVariant =
18
25
  | "neutralSurface"
19
26
  | "brandSoft"
@@ -72,6 +79,7 @@ type ChatSidebarToolbarProps = {
72
79
  onCreateSession: (sessionType: string) => void;
73
80
  onSelectNewSessionType: (sessionType: string) => void;
74
81
  onQueryChange: (query: string) => void;
82
+ collapsed?: boolean;
75
83
  };
76
84
 
77
85
  function getMobileCreateOptions(params: {
@@ -122,11 +130,73 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
122
130
  onCreateSession,
123
131
  onSelectNewSessionType,
124
132
  onQueryChange,
133
+ collapsed = false,
125
134
  } = props;
126
135
  const supportsSessionTypeSwitch = sessionTypeOptions.length > 1;
127
136
  const actionStyle =
128
137
  NEW_SESSION_ACTION_STYLE_CLASSES[NEW_SESSION_ACTION_STYLE_VARIANT];
129
138
 
139
+ if (collapsed) {
140
+ return (
141
+ <div className="px-2 pb-2">
142
+ <div
143
+ className={cn(
144
+ "flex flex-col items-center",
145
+ SIDEBAR_RAIL_ITEM_GAP_CLASS,
146
+ )}
147
+ >
148
+ <IconActionButton
149
+ icon={<Plus className={SIDEBAR_RAIL_ICON_CLASS} />}
150
+ label={t("chatSidebarNewTask")}
151
+ className={cn(
152
+ SIDEBAR_RAIL_CONTROL_CLASS,
153
+ SIDEBAR_RAIL_PRIMARY_SURFACE_CLASS,
154
+ )}
155
+ onClick={() => {
156
+ onCreateMenuOpenChange(false);
157
+ onCreateSession(selectedNewSessionType);
158
+ }}
159
+ />
160
+ {supportsSessionTypeSwitch ? (
161
+ <Popover
162
+ open={isCreateMenuOpen}
163
+ onOpenChange={onCreateMenuOpenChange}
164
+ >
165
+ <PopoverTrigger asChild>
166
+ <IconActionButton
167
+ icon={
168
+ <SessionTypeTriggerIcon
169
+ option={selectedNewSessionTypeOption}
170
+ />
171
+ }
172
+ label={t("chatSessionTypeLabel")}
173
+ className={cn(
174
+ SIDEBAR_RAIL_CONTROL_CLASS,
175
+ SIDEBAR_RAIL_SURFACE_CLASS,
176
+ )}
177
+ />
178
+ </PopoverTrigger>
179
+ <PopoverContent
180
+ align="start"
181
+ side="right"
182
+ className="w-56 rounded-2xl border border-gray-200/80 bg-white p-1.5 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
183
+ >
184
+ <ChatSessionTypeMenu
185
+ options={sessionTypeOptions}
186
+ selectedSessionType={selectedNewSessionType}
187
+ onSelect={(sessionType) => {
188
+ onSelectNewSessionType(sessionType);
189
+ onCreateMenuOpenChange(false);
190
+ }}
191
+ />
192
+ </PopoverContent>
193
+ </Popover>
194
+ ) : null}
195
+ </div>
196
+ </div>
197
+ );
198
+ }
199
+
130
200
  return (
131
201
  <>
132
202
  <div className="px-4 pb-3">
@@ -136,9 +206,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
136
206
  className={cn(
137
207
  "min-w-0 rounded-xl",
138
208
  actionStyle.leftClassName,
139
- supportsSessionTypeSwitch
140
- ? "flex-1 rounded-r-md"
141
- : "w-full",
209
+ supportsSessionTypeSwitch ? "flex-1 rounded-r-md" : "w-full",
142
210
  )}
143
211
  onClick={() => {
144
212
  onCreateMenuOpenChange(false);
@@ -157,7 +225,9 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
157
225
  <IconActionButton
158
226
  icon={
159
227
  <span className="inline-flex items-center gap-0.5">
160
- <SessionTypeTriggerIcon option={selectedNewSessionTypeOption} />
228
+ <SessionTypeTriggerIcon
229
+ option={selectedNewSessionTypeOption}
230
+ />
161
231
  <ChevronDown className="h-3 w-3 opacity-60" />
162
232
  </span>
163
233
  }
@@ -234,7 +304,10 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
234
304
  </div>
235
305
 
236
306
  {hasCreateMenu ? (
237
- <Popover open={isCreateMenuOpen} onOpenChange={onCreateMenuOpenChange}>
307
+ <Popover
308
+ open={isCreateMenuOpen}
309
+ onOpenChange={onCreateMenuOpenChange}
310
+ >
238
311
  <PopoverTrigger asChild>
239
312
  <IconActionButton
240
313
  icon={<Plus className="h-4 w-4" />}
@@ -1,8 +1,29 @@
1
- import { NavLink } from 'react-router-dom';
2
- import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
3
- import { Select, SelectContent, SelectItem, SelectTrigger } from '@/shared/components/ui/select';
4
- import { t, type I18nLanguage } from '@/shared/lib/i18n';
5
- import type { UiTheme } from '@/shared/lib/theme';
1
+ import { NavLink } from "react-router-dom";
2
+ import {
3
+ Popover,
4
+ PopoverContent,
5
+ PopoverTrigger,
6
+ } from "@/shared/components/ui/popover";
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ } from "@/shared/components/ui/select";
13
+ import {
14
+ Tooltip,
15
+ TooltipContent,
16
+ TooltipProvider,
17
+ TooltipTrigger,
18
+ } from "@/shared/components/ui/tooltip";
19
+ import { t, type I18nLanguage } from "@/shared/lib/i18n";
20
+ import type { UiTheme } from "@/shared/lib/theme";
21
+ import { cn } from "@/shared/lib/utils";
22
+ import {
23
+ SIDEBAR_RAIL_CONTROL_CLASS,
24
+ SIDEBAR_RAIL_ICON_CLASS,
25
+ SIDEBAR_RAIL_SURFACE_CLASS,
26
+ } from "@/app/components/layout/sidebar-rail.styles";
6
27
  import {
7
28
  BookOpen,
8
29
  ChevronRight,
@@ -11,7 +32,7 @@ import {
11
32
  Palette,
12
33
  Settings,
13
34
  type LucideIcon,
14
- } from 'lucide-react';
35
+ } from "lucide-react";
15
36
 
16
37
  type ChatSidebarUtilityOption<Value extends string> = {
17
38
  value: Value;
@@ -31,6 +52,7 @@ type ChatSidebarUtilityMenuProps = {
31
52
  onSelectLanguage: (language: I18nLanguage) => void;
32
53
  onOpenDocs: () => void;
33
54
  onOpenApps: () => void;
55
+ collapsed?: boolean;
34
56
  };
35
57
 
36
58
  export function ChatSidebarUtilityMenu({
@@ -46,6 +68,7 @@ export function ChatSidebarUtilityMenu({
46
68
  onSelectLanguage,
47
69
  onOpenDocs,
48
70
  onOpenApps,
71
+ collapsed = false,
49
72
  }: ChatSidebarUtilityMenuProps) {
50
73
  const handleOpenDocs = () => {
51
74
  onOpenDocs();
@@ -59,20 +82,54 @@ export function ChatSidebarUtilityMenu({
59
82
 
60
83
  return (
61
84
  <Popover open={isOpen} onOpenChange={onOpenChange}>
62
- <PopoverTrigger asChild>
63
- <button
64
- type="button"
65
- aria-label={t('settingsMenu')}
66
- className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-gray-600 transition-all duration-base hover:bg-gray-200/60 hover:text-gray-800"
67
- >
68
- <Settings className="h-4 w-4 shrink-0 text-gray-400" />
69
- <span className="min-w-0 flex-1 text-left">{t('settings')}</span>
70
- <span className="max-w-[112px] truncate text-[13px] text-gray-500">
71
- {currentThemeLabel} / {currentLanguageLabel}
72
- </span>
73
- </button>
74
- </PopoverTrigger>
75
- <PopoverContent side="top" align="start" className="w-64 p-2">
85
+ {collapsed ? (
86
+ <TooltipProvider delayDuration={250}>
87
+ <Tooltip>
88
+ <TooltipTrigger asChild>
89
+ <PopoverTrigger asChild>
90
+ <button
91
+ type="button"
92
+ aria-label={t("settingsMenu")}
93
+ className={cn(
94
+ "flex items-center justify-center",
95
+ SIDEBAR_RAIL_CONTROL_CLASS,
96
+ SIDEBAR_RAIL_SURFACE_CLASS,
97
+ )}
98
+ >
99
+ <Settings
100
+ className={cn(
101
+ SIDEBAR_RAIL_ICON_CLASS,
102
+ "shrink-0 text-gray-500",
103
+ )}
104
+ />
105
+ </button>
106
+ </PopoverTrigger>
107
+ </TooltipTrigger>
108
+ <TooltipContent side="right" className="text-xs">
109
+ {t("settingsMenu")}
110
+ </TooltipContent>
111
+ </Tooltip>
112
+ </TooltipProvider>
113
+ ) : (
114
+ <PopoverTrigger asChild>
115
+ <button
116
+ type="button"
117
+ aria-label={t("settingsMenu")}
118
+ className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-[13px] font-medium text-gray-600 transition-all duration-base hover:bg-gray-200/60 hover:text-gray-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35"
119
+ >
120
+ <Settings className="h-4 w-4 shrink-0 text-gray-400" />
121
+ <span className="min-w-0 flex-1 text-left">{t("settings")}</span>
122
+ <span className="max-w-[112px] truncate text-[13px] text-gray-500">
123
+ {currentThemeLabel} / {currentLanguageLabel}
124
+ </span>
125
+ </button>
126
+ </PopoverTrigger>
127
+ )}
128
+ <PopoverContent
129
+ side={collapsed ? "right" : "top"}
130
+ align={collapsed ? "end" : "start"}
131
+ className="w-64 p-2"
132
+ >
76
133
  <div className="space-y-1">
77
134
  <NavLink
78
135
  to="/settings"
@@ -80,7 +137,7 @@ export function ChatSidebarUtilityMenu({
80
137
  className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-gray-700 transition-colors hover:bg-gray-100"
81
138
  >
82
139
  <Settings className="h-4 w-4 text-gray-400" />
83
- <span className="flex-1 text-left">{t('settings')}</span>
140
+ <span className="flex-1 text-left">{t("settings")}</span>
84
141
  </NavLink>
85
142
  <button
86
143
  type="button"
@@ -88,7 +145,7 @@ export function ChatSidebarUtilityMenu({
88
145
  className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-gray-700 transition-colors hover:bg-gray-100"
89
146
  >
90
147
  <BookOpen className="h-4 w-4 text-gray-400" />
91
- <span className="flex-1 text-left">{t('docBrowserHelp')}</span>
148
+ <span className="flex-1 text-left">{t("docBrowserHelp")}</span>
92
149
  </button>
93
150
  <button
94
151
  type="button"
@@ -96,7 +153,7 @@ export function ChatSidebarUtilityMenu({
96
153
  className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] font-medium text-gray-700 transition-colors hover:bg-gray-100"
97
154
  >
98
155
  <Boxes className="h-4 w-4 text-gray-400" />
99
- <span className="flex-1 text-left">{t('appsTitle')}</span>
156
+ <span className="flex-1 text-left">{t("appsTitle")}</span>
100
157
  </button>
101
158
  </div>
102
159
 
@@ -104,7 +161,7 @@ export function ChatSidebarUtilityMenu({
104
161
 
105
162
  <ChatSidebarUtilitySelect
106
163
  icon={Palette}
107
- label={t('theme')}
164
+ label={t("theme")}
108
165
  options={themeOptions}
109
166
  value={currentTheme}
110
167
  valueLabel={currentThemeLabel}
@@ -114,7 +171,7 @@ export function ChatSidebarUtilityMenu({
114
171
 
115
172
  <ChatSidebarUtilitySelect
116
173
  icon={Languages}
117
- label={t('language')}
174
+ label={t("language")}
118
175
  options={languageOptions}
119
176
  value={currentLanguage}
120
177
  valueLabel={currentLanguageLabel}