@nextclaw/ui 0.14.1-beta.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 (179) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/assets/{api-dQ6uQSZG.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-CeFfbaqc.js → config-split-page-B5b9B0uE.js} +1 -1
  6. package/dist/assets/{confirm-dialog-C95Y4QPC.js → confirm-dialog-ClH_Ec7n.js} +1 -1
  7. package/dist/assets/{desktop-update-config-CcK6jiEb.js → desktop-update-config-DPNDnVpk.js} +1 -1
  8. package/dist/assets/{dist-DTGzeil6.js → dist-BtCS2Ucq.js} +1 -1
  9. package/dist/assets/{dist-fGogBmPA.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-D2F4KMiU.js → ellipsis-CtJXjIol.js} +1 -1
  14. package/dist/assets/{external-link-CcgtBK26.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-CTqdhFMk.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-C9fKWfVB.js → save-DKA0x4zk.js} +1 -1
  29. package/dist/assets/{search-D5FQ5q0X.js → search-BNlcoj5c.js} +1 -1
  30. package/dist/assets/{search-config-page-AMEJJNIS.js → search-config-page-Cq9uJ-N8.js} +1 -1
  31. package/dist/assets/{secrets-config-page-CcgJnvBO.js → secrets-config-page-CxSbZLBo.js} +2 -2
  32. package/dist/assets/{select-Cu5n0E-3.js → select-CvTlDZTQ.js} +1 -1
  33. package/dist/assets/{tag-chip-C5aOtjxu.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 +77 -12
  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 +182 -21
  60. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
  61. package/src/features/chat/components/layout/chat-sidebar.tsx +79 -93
  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/hooks/use-chat-model-favorites.ts +2 -1
  73. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  74. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  75. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  76. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  77. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  78. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  79. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  80. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  81. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  82. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  83. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  84. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  85. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  86. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  87. package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
  88. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  89. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  90. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  91. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -4
  92. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  93. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  94. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  95. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  96. package/src/features/chat/features/session-type/components/__tests__/{chat-sidebar-create-menu.test.tsx → chat-session-type-menu.test.tsx} +5 -5
  97. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +29 -1
  98. package/src/features/chat/features/session-type/components/{chat-sidebar-create-menu.tsx → chat-session-type-menu.tsx} +13 -9
  99. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +17 -5
  100. package/src/features/chat/features/session-type/hooks/__tests__/use-chat-new-session-type-preference.test.tsx +152 -0
  101. package/src/features/chat/features/session-type/hooks/use-chat-new-session-type-preference.ts +159 -0
  102. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  103. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
  104. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  105. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
  106. package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
  107. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  108. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  109. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  110. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  111. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  112. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  113. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  114. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  115. package/src/features/chat/index.ts +0 -1
  116. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  117. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  118. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  119. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  120. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  121. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  122. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  123. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  124. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  125. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  126. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +24 -0
  127. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +8 -3
  128. package/src/features/marketplace/components/marketplace-item-list-view.tsx +2 -2
  129. package/src/features/marketplace/components/marketplace-list-card.tsx +124 -98
  130. package/src/features/marketplace/components/marketplace-page-parts.tsx +11 -6
  131. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  132. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  133. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  134. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  135. package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -1
  136. package/src/shared/components/common/agent-avatar.tsx +5 -2
  137. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  138. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  139. package/src/shared/lib/api/index.ts +1 -0
  140. package/src/shared/lib/api/preferences/preference-keys.config.ts +9 -0
  141. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  142. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  143. package/src/shared/lib/i18n/locales/en-US/marketplace.json +2 -0
  144. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  145. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  146. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +2 -0
  147. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  148. package/src/shared/lib/ui-document-title/index.ts +50 -21
  149. package/dist/assets/channels-list-page-DfpjB8XB.js +0 -8
  150. package/dist/assets/chat-page-D3QaEqjs.js +0 -105
  151. package/dist/assets/doc-browser-CuBTcBEZ.js +0 -1
  152. package/dist/assets/doc-browser-DVHhEb96.js +0 -1
  153. package/dist/assets/doc-browser-context-BfMXwnrz.js +0 -1
  154. package/dist/assets/index-ByJCbQqr.css +0 -1
  155. package/dist/assets/index-DjXqiloY.js +0 -103
  156. package/dist/assets/mcp-marketplace-page-98-V__3u.js +0 -1
  157. package/dist/assets/mcp-marketplace-page-BNq_PNmO.js +0 -40
  158. package/dist/assets/model-config-page-aAUkZkSX.js +0 -1
  159. package/dist/assets/plus-1Rxl5FlV.js +0 -1
  160. package/dist/assets/provider-scoped-model-input-we4_Z4gP.js +0 -1
  161. package/dist/assets/providers-config-page-CtMkPSyu.js +0 -1
  162. package/dist/assets/remote-fQ7N_r8u.js +0 -1
  163. package/dist/assets/rotate-cw-CzfIeAJj.js +0 -1
  164. package/dist/assets/runtime-config-page-TD1UrTbG.js +0 -1
  165. package/dist/assets/use-config-D8TESeo6.js +0 -1
  166. package/dist/assets/x-1qETdQ70.js +0 -1
  167. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  168. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  169. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  170. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  171. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  172. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  173. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  174. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  175. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  176. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  177. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  178. package/src/features/chat/stores/chat-input.store.ts +0 -104
  179. /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
+ }
@@ -1,4 +1,4 @@
1
- import { Button } from "@/shared/components/ui/button";
1
+ import { Button, type ButtonProps } from "@/shared/components/ui/button";
2
2
  import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
3
3
  import { Input } from "@/shared/components/ui/input";
4
4
  import {
@@ -6,23 +6,80 @@ import {
6
6
  PopoverContent,
7
7
  PopoverTrigger,
8
8
  } from "@/shared/components/ui/popover";
9
- import { ChatSidebarCreateMenu } from "@/features/chat/features/session-type/components/chat-sidebar-create-menu";
10
- import type { ChatInputSnapshot } from "@/features/chat/stores/chat-input.store";
9
+ import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
10
+ import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
11
+ import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
11
12
  import { cn } from "@/shared/lib/utils";
12
13
  import { t } from "@/shared/lib/i18n";
13
- import { ChevronDown, Plus, Search } from "lucide-react";
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";
22
+
23
+ type SessionTypeOption = ChatSessionTypeOption;
24
+ type NewSessionActionStyleVariant =
25
+ | "neutralSurface"
26
+ | "brandSoft"
27
+ | "brandTextSurface"
28
+ | "brandSolid";
14
29
 
15
- type SessionTypeOption = ChatInputSnapshot["sessionTypeOptions"][number];
30
+ const NEW_SESSION_ACTION_STYLE_VARIANT: NewSessionActionStyleVariant =
31
+ "brandSoft";
32
+
33
+ const NEW_SESSION_ACTION_STYLE_CLASSES: Record<
34
+ NewSessionActionStyleVariant,
35
+ {
36
+ leftVariant: ButtonProps["variant"];
37
+ leftClassName: string;
38
+ rightClassName: string;
39
+ }
40
+ > = {
41
+ neutralSurface: {
42
+ leftVariant: "ghost",
43
+ leftClassName:
44
+ "bg-white/90 text-gray-700 shadow-none transition-[background-color,color,box-shadow] hover:bg-white hover:text-gray-950 hover:shadow-sm active:bg-white/95",
45
+ rightClassName:
46
+ "bg-white/90 text-gray-600 shadow-none transition-[background-color,color,box-shadow] hover:bg-white hover:text-gray-900 hover:shadow-sm active:bg-white/95",
47
+ },
48
+ brandSoft: {
49
+ leftVariant: "ghost",
50
+ leftClassName:
51
+ "bg-primary/10 text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
52
+ rightClassName:
53
+ "bg-primary/10 text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-primary/15 hover:text-primary-700 hover:shadow-sm active:bg-primary/20",
54
+ },
55
+ brandTextSurface: {
56
+ leftVariant: "ghost",
57
+ leftClassName:
58
+ "bg-white/90 text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-primary/10 hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
59
+ rightClassName:
60
+ "bg-white/90 text-primary shadow-none transition-[background-color,color,box-shadow] hover:bg-primary/10 hover:text-primary-700 hover:shadow-sm active:bg-primary/15",
61
+ },
62
+ brandSolid: {
63
+ leftVariant: "primary",
64
+ leftClassName: "",
65
+ rightClassName:
66
+ "bg-primary text-primary-foreground shadow-sm hover:bg-primary-600 hover:text-primary-foreground active:bg-primary-700",
67
+ },
68
+ };
16
69
 
17
70
  type ChatSidebarToolbarProps = {
18
71
  query: string;
19
72
  defaultSessionType: string;
20
73
  sessionTypeOptions: SessionTypeOption[];
21
74
  nonDefaultSessionTypeOptions: SessionTypeOption[];
75
+ selectedNewSessionType: string;
76
+ selectedNewSessionTypeOption: SessionTypeOption | null;
22
77
  isCreateMenuOpen: boolean;
23
78
  onCreateMenuOpenChange: (open: boolean) => void;
24
79
  onCreateSession: (sessionType: string) => void;
80
+ onSelectNewSessionType: (sessionType: string) => void;
25
81
  onQueryChange: (query: string) => void;
82
+ collapsed?: boolean;
26
83
  };
27
84
 
28
85
  function getMobileCreateOptions(params: {
@@ -39,58 +96,158 @@ function getMobileCreateOptions(params: {
39
96
  ];
40
97
  }
41
98
 
99
+ function SessionTypeTriggerIcon({
100
+ option,
101
+ }: {
102
+ option: SessionTypeOption | null;
103
+ }) {
104
+ if (option?.icon?.src) {
105
+ return (
106
+ <span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
107
+ <SessionContextIconNode
108
+ icon={{
109
+ kind: "runtime-image",
110
+ src: option.icon.src,
111
+ alt: option.icon.alt ?? null,
112
+ name: option.label,
113
+ }}
114
+ className="h-4 w-4"
115
+ />
116
+ </span>
117
+ );
118
+ }
119
+ return <Bot className="h-4 w-4 shrink-0" />;
120
+ }
121
+
42
122
  export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
43
123
  const {
44
124
  query,
45
- defaultSessionType,
46
- nonDefaultSessionTypeOptions,
125
+ selectedNewSessionType,
126
+ selectedNewSessionTypeOption,
127
+ sessionTypeOptions,
47
128
  isCreateMenuOpen,
48
129
  onCreateMenuOpenChange,
49
130
  onCreateSession,
131
+ onSelectNewSessionType,
50
132
  onQueryChange,
133
+ collapsed = false,
51
134
  } = props;
135
+ const supportsSessionTypeSwitch = sessionTypeOptions.length > 1;
136
+ const actionStyle =
137
+ NEW_SESSION_ACTION_STYLE_CLASSES[NEW_SESSION_ACTION_STYLE_VARIANT];
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
+ }
52
199
 
53
200
  return (
54
201
  <>
55
202
  <div className="px-4 pb-3">
56
203
  <div className="flex items-center gap-2">
57
204
  <Button
58
- variant="primary"
205
+ variant={actionStyle.leftVariant}
59
206
  className={cn(
60
207
  "min-w-0 rounded-xl",
61
- nonDefaultSessionTypeOptions.length > 0
62
- ? "flex-1 rounded-r-md"
63
- : "w-full",
208
+ actionStyle.leftClassName,
209
+ supportsSessionTypeSwitch ? "flex-1 rounded-r-md" : "w-full",
64
210
  )}
65
211
  onClick={() => {
66
212
  onCreateMenuOpenChange(false);
67
- onCreateSession(defaultSessionType);
213
+ onCreateSession(selectedNewSessionType);
68
214
  }}
69
215
  >
70
216
  <Plus className="mr-2 h-4 w-4" />
71
217
  {t("chatSidebarNewTask")}
72
218
  </Button>
73
- {nonDefaultSessionTypeOptions.length > 0 ? (
219
+ {supportsSessionTypeSwitch ? (
74
220
  <Popover
75
221
  open={isCreateMenuOpen}
76
222
  onOpenChange={onCreateMenuOpenChange}
77
223
  >
78
224
  <PopoverTrigger asChild>
79
225
  <IconActionButton
80
- icon={<ChevronDown className="h-4 w-4" />}
226
+ icon={
227
+ <span className="inline-flex items-center gap-0.5">
228
+ <SessionTypeTriggerIcon
229
+ option={selectedNewSessionTypeOption}
230
+ />
231
+ <ChevronDown className="h-3 w-3 opacity-60" />
232
+ </span>
233
+ }
81
234
  label={t("chatSessionTypeLabel")}
82
- tooltip={false}
83
- className="h-9 w-10 shrink-0 rounded-xl rounded-l-md bg-primary text-primary-foreground shadow-sm hover:bg-primary-600 hover:text-primary-foreground active:bg-primary-700"
235
+ tooltip={t("chatSessionTypeLabel")}
236
+ className={cn(
237
+ "h-9 w-12 shrink-0 rounded-xl rounded-l-md",
238
+ actionStyle.rightClassName,
239
+ )}
84
240
  />
85
241
  </PopoverTrigger>
86
242
  <PopoverContent
87
243
  align="end"
88
244
  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)]"
89
245
  >
90
- <ChatSidebarCreateMenu
91
- options={nonDefaultSessionTypeOptions}
246
+ <ChatSessionTypeMenu
247
+ options={sessionTypeOptions}
248
+ selectedSessionType={selectedNewSessionType}
92
249
  onSelect={(sessionType) => {
93
- onCreateSession(sessionType);
250
+ onSelectNewSessionType(sessionType);
94
251
  onCreateMenuOpenChange(false);
95
252
  }}
96
253
  />
@@ -147,7 +304,10 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
147
304
  </div>
148
305
 
149
306
  {hasCreateMenu ? (
150
- <Popover open={isCreateMenuOpen} onOpenChange={onCreateMenuOpenChange}>
307
+ <Popover
308
+ open={isCreateMenuOpen}
309
+ onOpenChange={onCreateMenuOpenChange}
310
+ >
151
311
  <PopoverTrigger asChild>
152
312
  <IconActionButton
153
313
  icon={<Plus className="h-4 w-4" />}
@@ -160,8 +320,9 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
160
320
  align="end"
161
321
  className="w-60 rounded-3xl border border-gray-200/80 bg-white p-2 shadow-[0_24px_70px_-30px_rgba(15,23,42,0.45)]"
162
322
  >
163
- <ChatSidebarCreateMenu
323
+ <ChatSessionTypeMenu
164
324
  options={createOptions}
325
+ selectedSessionType={defaultSessionType}
165
326
  title={t("chatSidebarNewTask")}
166
327
  titleClassName="pb-1.5 text-[11px] font-medium normal-case tracking-normal"
167
328
  onSelect={(sessionType) => {