@vibe-forge/client 0.2.0-alpha.0

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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/cli.cjs +6 -0
  3. package/index.html +27 -0
  4. package/package.json +42 -0
  5. package/src/App.tsx +174 -0
  6. package/src/api.ts +241 -0
  7. package/src/components/ArchiveView.scss +168 -0
  8. package/src/components/ArchiveView.tsx +299 -0
  9. package/src/components/AutomationView/AutomationView.scss +26 -0
  10. package/src/components/AutomationView/RuleFormPanel.scss +129 -0
  11. package/src/components/AutomationView/RuleFormPanel.tsx +257 -0
  12. package/src/components/AutomationView/RuleSidebar.scss +219 -0
  13. package/src/components/AutomationView/RuleSidebar.tsx +258 -0
  14. package/src/components/AutomationView/RunHistoryPanel.scss +286 -0
  15. package/src/components/AutomationView/RunHistoryPanel.tsx +320 -0
  16. package/src/components/AutomationView/TaskList.scss +128 -0
  17. package/src/components/AutomationView/TaskList.tsx +79 -0
  18. package/src/components/AutomationView/TriggerList.scss +153 -0
  19. package/src/components/AutomationView/TriggerList.tsx +217 -0
  20. package/src/components/AutomationView/index.tsx +228 -0
  21. package/src/components/AutomationView/types.ts +21 -0
  22. package/src/components/Chat.scss +89 -0
  23. package/src/components/Chat.tsx +92 -0
  24. package/src/components/ConfigView.scss +185 -0
  25. package/src/components/ConfigView.tsx +258 -0
  26. package/src/components/NavRail.scss +71 -0
  27. package/src/components/NavRail.tsx +188 -0
  28. package/src/components/Sidebar.scss +112 -0
  29. package/src/components/Sidebar.tsx +291 -0
  30. package/src/components/chat/ChatHeader.scss +401 -0
  31. package/src/components/chat/ChatHeader.tsx +342 -0
  32. package/src/components/chat/ChatHistoryView.tsx +122 -0
  33. package/src/components/chat/ChatSettingsView.tsx +22 -0
  34. package/src/components/chat/ChatTimelineView.scss +53 -0
  35. package/src/components/chat/ChatTimelineView.tsx +158 -0
  36. package/src/components/chat/CodeBlock.scss +87 -0
  37. package/src/components/chat/CodeBlock.tsx +179 -0
  38. package/src/components/chat/CompletionMenu.scss +70 -0
  39. package/src/components/chat/CompletionMenu.tsx +58 -0
  40. package/src/components/chat/CurrentTodoList.scss +217 -0
  41. package/src/components/chat/CurrentTodoList.tsx +103 -0
  42. package/src/components/chat/MarkdownContent.tsx +43 -0
  43. package/src/components/chat/MessageFooter.tsx +48 -0
  44. package/src/components/chat/MessageItem.scss +251 -0
  45. package/src/components/chat/MessageItem.tsx +78 -0
  46. package/src/components/chat/NewSessionGuide.scss +186 -0
  47. package/src/components/chat/NewSessionGuide.tsx +167 -0
  48. package/src/components/chat/Sender.scss +367 -0
  49. package/src/components/chat/Sender.tsx +541 -0
  50. package/src/components/chat/SessionTimelinePanel/EventList.scss +58 -0
  51. package/src/components/chat/SessionTimelinePanel/EventList.tsx +212 -0
  52. package/src/components/chat/SessionTimelinePanel/gantt.ts +177 -0
  53. package/src/components/chat/SessionTimelinePanel/git-graph.ts +518 -0
  54. package/src/components/chat/SessionTimelinePanel/index.scss +28 -0
  55. package/src/components/chat/SessionTimelinePanel/index.tsx +121 -0
  56. package/src/components/chat/SessionTimelinePanel/mermaid.ts +4 -0
  57. package/src/components/chat/SessionTimelinePanel/types.ts +64 -0
  58. package/src/components/chat/SessionTimelinePanel/utils.ts +20 -0
  59. package/src/components/chat/ThinkingStatus.scss +70 -0
  60. package/src/components/chat/ThinkingStatus.tsx +13 -0
  61. package/src/components/chat/ToolCallBox.scss +137 -0
  62. package/src/components/chat/ToolCallBox.tsx +55 -0
  63. package/src/components/chat/ToolGroup.scss +154 -0
  64. package/src/components/chat/ToolGroup.tsx +102 -0
  65. package/src/components/chat/ToolRenderer.tsx +45 -0
  66. package/src/components/chat/messageUtils.ts +171 -0
  67. package/src/components/chat/safeSerialize.ts +84 -0
  68. package/src/components/chat/tools/DefaultTool.tsx +63 -0
  69. package/src/components/chat/tools/adapter-claude/BashTool.scss +71 -0
  70. package/src/components/chat/tools/adapter-claude/BashTool.tsx +82 -0
  71. package/src/components/chat/tools/adapter-claude/GlobTool.scss +88 -0
  72. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +85 -0
  73. package/src/components/chat/tools/adapter-claude/GrepTool.scss +96 -0
  74. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +114 -0
  75. package/src/components/chat/tools/adapter-claude/LSTool.scss +85 -0
  76. package/src/components/chat/tools/adapter-claude/LSTool.tsx +94 -0
  77. package/src/components/chat/tools/adapter-claude/ReadTool.scss +57 -0
  78. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +87 -0
  79. package/src/components/chat/tools/adapter-claude/TodoTool.scss +78 -0
  80. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +60 -0
  81. package/src/components/chat/tools/adapter-claude/WriteTool.scss +92 -0
  82. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +86 -0
  83. package/src/components/chat/tools/adapter-claude/components/FileList.scss +65 -0
  84. package/src/components/chat/tools/adapter-claude/components/FileList.tsx +185 -0
  85. package/src/components/chat/tools/adapter-claude/index.ts +28 -0
  86. package/src/components/chat/tools/defineToolRender.ts +28 -0
  87. package/src/components/chat/tools/task/GetTaskInfoTool.scss +50 -0
  88. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +88 -0
  89. package/src/components/chat/tools/task/ListTasksTool.scss +56 -0
  90. package/src/components/chat/tools/task/ListTasksTool.tsx +83 -0
  91. package/src/components/chat/tools/task/StartTasksTool.scss +56 -0
  92. package/src/components/chat/tools/task/StartTasksTool.tsx +96 -0
  93. package/src/components/chat/tools/task/components/TaskToolCard.scss +127 -0
  94. package/src/components/chat/tools/task/components/TaskToolCard.tsx +177 -0
  95. package/src/components/chat/tools/task/index.ts +15 -0
  96. package/src/components/chat/useChatModels.tsx +206 -0
  97. package/src/components/chat/useChatSession.ts +370 -0
  98. package/src/components/config/ConfigAboutSection.scss +111 -0
  99. package/src/components/config/ConfigAboutSection.tsx +86 -0
  100. package/src/components/config/ConfigDisplayValue.scss +22 -0
  101. package/src/components/config/ConfigDisplayValue.tsx +62 -0
  102. package/src/components/config/ConfigEditors.scss +65 -0
  103. package/src/components/config/ConfigEditors.tsx +98 -0
  104. package/src/components/config/ConfigFieldRow.scss +97 -0
  105. package/src/components/config/ConfigFieldRow.tsx +36 -0
  106. package/src/components/config/ConfigSectionForm.scss +94 -0
  107. package/src/components/config/ConfigSectionForm.tsx +436 -0
  108. package/src/components/config/ConfigSectionPanel.tsx +67 -0
  109. package/src/components/config/ConfigShortcutInput.scss +11 -0
  110. package/src/components/config/ConfigShortcutInput.tsx +52 -0
  111. package/src/components/config/ConfigSourceSwitch.tsx +57 -0
  112. package/src/components/config/configSchema.ts +319 -0
  113. package/src/components/config/configUtils.ts +83 -0
  114. package/src/components/config/index.tsx +5 -0
  115. package/src/components/config/recordEditors/BooleanRecordEditor.scss +1 -0
  116. package/src/components/config/recordEditors/BooleanRecordEditor.tsx +75 -0
  117. package/src/components/config/recordEditors/KeyValueEditor.scss +1 -0
  118. package/src/components/config/recordEditors/KeyValueEditor.tsx +97 -0
  119. package/src/components/config/recordEditors/McpServersRecordEditor.scss +1 -0
  120. package/src/components/config/recordEditors/McpServersRecordEditor.tsx +258 -0
  121. package/src/components/config/recordEditors/ModelServicesRecordEditor.scss +1 -0
  122. package/src/components/config/recordEditors/ModelServicesRecordEditor.tsx +233 -0
  123. package/src/components/config/recordEditors/RecordEditors.scss +117 -0
  124. package/src/components/config/recordEditors/RecordJsonEditor.scss +1 -0
  125. package/src/components/config/recordEditors/RecordJsonEditor.tsx +113 -0
  126. package/src/components/config/recordEditors/index.tsx +5 -0
  127. package/src/components/knowledge-base/KnowledgeBaseView.scss +19 -0
  128. package/src/components/knowledge-base/KnowledgeBaseView.tsx +186 -0
  129. package/src/components/knowledge-base/components/ActionButton.scss +5 -0
  130. package/src/components/knowledge-base/components/ActionButton.tsx +9 -0
  131. package/src/components/knowledge-base/components/EmptyState.scss +19 -0
  132. package/src/components/knowledge-base/components/EmptyState.tsx +42 -0
  133. package/src/components/knowledge-base/components/EntitiesTab.scss +5 -0
  134. package/src/components/knowledge-base/components/EntitiesTab.tsx +80 -0
  135. package/src/components/knowledge-base/components/EntityItem.scss +82 -0
  136. package/src/components/knowledge-base/components/EntityItem.tsx +79 -0
  137. package/src/components/knowledge-base/components/EntityList.scss +5 -0
  138. package/src/components/knowledge-base/components/EntityList.tsx +70 -0
  139. package/src/components/knowledge-base/components/FilterBar.scss +21 -0
  140. package/src/components/knowledge-base/components/FilterBar.tsx +51 -0
  141. package/src/components/knowledge-base/components/FlowsTab.scss +5 -0
  142. package/src/components/knowledge-base/components/FlowsTab.tsx +80 -0
  143. package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +27 -0
  144. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +29 -0
  145. package/src/components/knowledge-base/components/KnowledgeList.scss +19 -0
  146. package/src/components/knowledge-base/components/KnowledgeList.tsx +19 -0
  147. package/src/components/knowledge-base/components/LoadingState.scss +5 -0
  148. package/src/components/knowledge-base/components/LoadingState.tsx +11 -0
  149. package/src/components/knowledge-base/components/MetaList.scss +19 -0
  150. package/src/components/knowledge-base/components/MetaList.tsx +18 -0
  151. package/src/components/knowledge-base/components/RulesTab.scss +5 -0
  152. package/src/components/knowledge-base/components/RulesTab.tsx +49 -0
  153. package/src/components/knowledge-base/components/SectionHeader.scss +22 -0
  154. package/src/components/knowledge-base/components/SectionHeader.tsx +21 -0
  155. package/src/components/knowledge-base/components/SkillsTab.scss +5 -0
  156. package/src/components/knowledge-base/components/SkillsTab.tsx +49 -0
  157. package/src/components/knowledge-base/components/SpecItem.scss +138 -0
  158. package/src/components/knowledge-base/components/SpecItem.tsx +131 -0
  159. package/src/components/knowledge-base/components/SpecList.scss +5 -0
  160. package/src/components/knowledge-base/components/SpecList.tsx +70 -0
  161. package/src/components/knowledge-base/components/TabContent.scss +8 -0
  162. package/src/components/knowledge-base/components/TabContent.tsx +17 -0
  163. package/src/components/knowledge-base/components/TabLabel.scss +10 -0
  164. package/src/components/knowledge-base/components/TabLabel.tsx +15 -0
  165. package/src/components/knowledge-base/index.tsx +1 -0
  166. package/src/components/sidebar/SessionItem.scss +256 -0
  167. package/src/components/sidebar/SessionItem.tsx +265 -0
  168. package/src/components/sidebar/SessionList.scss +92 -0
  169. package/src/components/sidebar/SessionList.tsx +166 -0
  170. package/src/components/sidebar/SidebarHeader.scss +79 -0
  171. package/src/components/sidebar/SidebarHeader.tsx +128 -0
  172. package/src/connectionManager.ts +172 -0
  173. package/src/hooks/useGlobalShortcut.ts +26 -0
  174. package/src/hooks/useQueryParams.ts +54 -0
  175. package/src/i18n.ts +22 -0
  176. package/src/main.tsx +41 -0
  177. package/src/resources/locales/en.json +765 -0
  178. package/src/resources/locales/zh.json +766 -0
  179. package/src/store/index.ts +23 -0
  180. package/src/styles/global.scss +100 -0
  181. package/src/utils/shortcutUtils.ts +88 -0
  182. package/src/vite-env.d.ts +12 -0
  183. package/src/ws.ts +33 -0
  184. package/vite.config.ts +26 -0
@@ -0,0 +1,112 @@
1
+ .sidebar-container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ --sidebar-hover-bg: #e5e5e5;
5
+ --sidebar-active-bg: #eff6ff;
6
+ --sidebar-active-text: #2563eb;
7
+ --sidebar-btn-color: #9ca3af;
8
+ --sidebar-btn-hover-bg: #fff1f0;
9
+
10
+ background-color: var(--sub-bg-color);
11
+ border-right: 1px solid var(--sub-border-color);
12
+ z-index: 5;
13
+ box-sizing: border-box;
14
+ height: 100%;
15
+
16
+ .sidebar-content {
17
+ height: 100%;
18
+ display: flex;
19
+ flex-direction: column;
20
+
21
+ .sidebar-new-chat {
22
+ padding: 0 16px;
23
+ flex-shrink: 0;
24
+
25
+ .new-chat-btn {
26
+ height: 36px;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ padding: 0 12px;
31
+ font-weight: 500;
32
+ transition: all .2s cubic-bezier(.4, 0, .2, 1);
33
+
34
+ .btn-content {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 2px;
38
+ }
39
+
40
+ .material-symbols-rounded {
41
+ font-size: 18px;
42
+ }
43
+
44
+ .shortcut-tag {
45
+ font-size: 12px;
46
+ opacity: .8;
47
+ font-family:
48
+ ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation
49
+ Mono, monospace;
50
+ background: rgba(255, 255, 255, .2);
51
+ padding: 2px 6px;
52
+ border-radius: 4px;
53
+ line-height: 1;
54
+ }
55
+
56
+ &.active-scale {
57
+ transform: scale(.96);
58
+ }
59
+
60
+ // Active State Styles
61
+ &.active {
62
+ background-color: transparent !important;
63
+ border: 1px solid var(--sidebar-active-text) !important;
64
+ color: var(--sidebar-active-text) !important;
65
+ opacity: 1 !important;
66
+ cursor: default;
67
+ box-shadow: none;
68
+
69
+ .shortcut-tag {
70
+ display: none;
71
+ }
72
+
73
+ &:hover {
74
+ background-color: transparent !important;
75
+ color: var(--sidebar-active-text) !important;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ * {
83
+ box-sizing: border-box;
84
+ }
85
+
86
+ &.collapsed {
87
+ border-right-width: 0 !important;
88
+ .sidebar-collapse-btn {
89
+ margin-left: 20px;
90
+ }
91
+ .sidebar-new-chat-btn {
92
+ margin-right: -112px;
93
+ }
94
+ }
95
+ }
96
+
97
+ .sidebar-footer {
98
+ margin-top: auto;
99
+ border-top: 1px solid var(--border-color);
100
+ padding: 0;
101
+ display: flex;
102
+ justify-content: flex-end;
103
+ align-items: center;
104
+ }
105
+
106
+ html.dark .sidebar-container {
107
+ --sidebar-hover-bg: #1f1f1f;
108
+ --sidebar-active-bg: #111b26;
109
+ --sidebar-active-text: #3b82f6;
110
+ --sidebar-btn-color: #595959;
111
+ --sidebar-btn-hover-bg: #2a1215;
112
+ }
@@ -0,0 +1,291 @@
1
+ import './Sidebar.scss'
2
+
3
+ import { Button, Tooltip } from 'antd'
4
+ import { useAtom, useAtomValue } from 'jotai'
5
+ import React, { useMemo, useRef, useState } from 'react'
6
+ import { useTranslation } from 'react-i18next'
7
+ import useSWR from 'swr'
8
+
9
+ import type { Session } from '@vibe-forge/core'
10
+ import { useQueryParams } from '#~/hooks/useQueryParams.js'
11
+ import { deleteSession, updateSession } from '../api'
12
+ import { useGlobalShortcut } from '../hooks/useGlobalShortcut'
13
+ import { isSidebarCollapsedAtom, isSidebarResizingAtom } from '../store/index'
14
+ import { formatShortcutLabel } from '../utils/shortcutUtils'
15
+ import { SessionList } from './sidebar/SessionList'
16
+ import { SidebarHeader } from './sidebar/SidebarHeader'
17
+
18
+ export function Sidebar({
19
+ activeId,
20
+ onSelectSession,
21
+ onDeletedSession,
22
+ width
23
+ }: {
24
+ activeId?: string
25
+ onSelectSession: (session: Session, isNew?: boolean) => void
26
+ onDeletedSession?: (id: string, nextId?: string) => void
27
+ width: number
28
+ }) {
29
+ const { t } = useTranslation()
30
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useAtom(isSidebarCollapsedAtom)
31
+ const isResizing = useAtomValue(isSidebarResizingAtom)
32
+ const [searchQuery, setSearchQuery] = useState('')
33
+ const [isBatchMode, setIsBatchMode] = useState(false)
34
+ const [selectedIds, setSelectedIds] = useState(new Set<string>())
35
+ const isMac = navigator.platform.includes('Mac')
36
+ const { values } = useQueryParams<{ tag: string }>({
37
+ keys: ['tag'],
38
+ defaults: { tag: '' },
39
+ omit: { tag: (value) => value === '' }
40
+ })
41
+ const tagFilter = values.tag.trim()
42
+
43
+ const { data: sessionsRes, mutate: mutateSessions } = useSWR<{ sessions: Session[] }>(
44
+ `/api/sessions`
45
+ )
46
+ const sessions: Session[] = sessionsRes?.sessions ?? []
47
+ const { data: configRes } = useSWR<{
48
+ sources?: {
49
+ merged?: {
50
+ shortcuts?: {
51
+ newSession?: string
52
+ }
53
+ }
54
+ }
55
+ }>('/api/config')
56
+
57
+ const newSessionShortcut = configRes?.sources?.merged?.shortcuts?.newSession
58
+ const resolvedNewSessionShortcut = newSessionShortcut != null && newSessionShortcut.trim() !== ''
59
+ ? newSessionShortcut
60
+ : 'mod+k'
61
+ const shortcutLabel = useMemo(
62
+ () => formatShortcutLabel(resolvedNewSessionShortcut, isMac),
63
+ [resolvedNewSessionShortcut, isMac]
64
+ )
65
+
66
+ const filteredSessions = useMemo(() => {
67
+ const query = searchQuery.trim().toLowerCase()
68
+ return sessions.filter((s: Session) => {
69
+ if (tagFilter) {
70
+ const matchesTag = (s.tags ?? []).some((tag) => tag.toLowerCase() === tagFilter.toLowerCase())
71
+ if (!matchesTag) return false
72
+ }
73
+ if (!query) return true
74
+ return (
75
+ (s.title ?? '').toLowerCase().includes(query) ||
76
+ (s.lastMessage ?? '').toLowerCase().includes(query) ||
77
+ (s.lastUserMessage ?? '').toLowerCase().includes(query) ||
78
+ s.id.toLowerCase().includes(query) ||
79
+ (s.tags ?? []).some((tag: string) => tag.toLowerCase().includes(query))
80
+ )
81
+ })
82
+ }, [sessions, searchQuery, tagFilter])
83
+
84
+ async function handleCreateSession() {
85
+ onSelectSession({ id: '' } as Session, true)
86
+ }
87
+
88
+ async function handleArchiveSession(id: string) {
89
+ // 先计算下一个要跳转的 ID
90
+ let nextId: string | undefined
91
+ const currentIndex = sessions.findIndex(s => s.id === id)
92
+ if (currentIndex !== -1) {
93
+ if (currentIndex + 1 < sessions.length) {
94
+ nextId = sessions[currentIndex + 1].id
95
+ } else if (currentIndex - 1 >= 0) {
96
+ nextId = sessions[currentIndex - 1].id
97
+ }
98
+ }
99
+
100
+ try {
101
+ await updateSession(id, { isArchived: true })
102
+ await mutateSessions()
103
+ // 传递 nextId 给 onDeletedSession
104
+ onDeletedSession?.(id, nextId)
105
+ } catch (err) {
106
+ console.error('Failed to archive session:', err)
107
+ }
108
+ }
109
+
110
+ async function handleDeleteSession(id: string) {
111
+ // 先计算下一个要跳转的 ID
112
+ let nextId: string | undefined
113
+ const currentIndex = sessions.findIndex(s => s.id === id)
114
+ if (currentIndex !== -1) {
115
+ if (currentIndex + 1 < sessions.length) {
116
+ nextId = sessions[currentIndex + 1].id
117
+ } else if (currentIndex - 1 >= 0) {
118
+ nextId = sessions[currentIndex - 1].id
119
+ }
120
+ }
121
+
122
+ try {
123
+ await deleteSession(id)
124
+ await mutateSessions()
125
+ if (activeId === id) onDeletedSession?.(id, nextId)
126
+ } catch (err) {
127
+ console.error('Failed to delete session:', err)
128
+ }
129
+ }
130
+
131
+ async function handleStarSession(id: string, isStarred: boolean) {
132
+ try {
133
+ await updateSession(id, { isStarred })
134
+ await mutateSessions()
135
+ } catch (err) {
136
+ console.error('Failed to star session:', err)
137
+ }
138
+ }
139
+
140
+ async function handleUpdateTags(id: string, tags: string[]) {
141
+ try {
142
+ await updateSession(id, { tags })
143
+ await mutateSessions()
144
+ } catch (err) {
145
+ console.error('Failed to update tags:', err)
146
+ }
147
+ }
148
+
149
+ const handleToggleSelect = (id: string) => {
150
+ setSelectedIds((prev: Set<string>) => {
151
+ const next = new Set(prev)
152
+ if (next.has(id)) {
153
+ next.delete(id)
154
+ } else {
155
+ next.add(id)
156
+ }
157
+ return next
158
+ })
159
+ }
160
+
161
+ const handleSelectAll = (selected: boolean) => {
162
+ if (selected) {
163
+ setSelectedIds(new Set(filteredSessions.map(s => s.id)))
164
+ } else {
165
+ setSelectedIds(new Set())
166
+ }
167
+ }
168
+
169
+ const handleBatchArchive = async () => {
170
+ try {
171
+ await Promise.all(Array.from(selectedIds).map(async (id: string) => updateSession(id, { isArchived: true })))
172
+ await mutateSessions()
173
+
174
+ // Calculate nextId if active session is archived
175
+ if (activeId && selectedIds.has(activeId)) {
176
+ let nextId: string | undefined
177
+ // Find the first session that is NOT in the selectedIds list
178
+ const nextSession = sessions.find(s => !selectedIds.has(s.id))
179
+ if (nextSession) {
180
+ nextId = nextSession.id
181
+ }
182
+ onDeletedSession?.(activeId, nextId)
183
+ }
184
+
185
+ setSelectedIds(new Set<string>())
186
+ setIsBatchMode(false)
187
+ } catch (err) {
188
+ console.error('Failed to batch archive sessions:', err)
189
+ }
190
+ }
191
+
192
+ const toggleBatchMode = () => {
193
+ setIsBatchMode((prev: boolean) => !prev)
194
+ setSelectedIds(new Set<string>())
195
+ }
196
+
197
+ const isCreatingSession = activeId === undefined || activeId === ''
198
+
199
+ const createBtnRef = useRef<HTMLButtonElement>(null)
200
+
201
+ useGlobalShortcut({
202
+ shortcut: resolvedNewSessionShortcut,
203
+ isMac,
204
+ onTrigger: (event) => {
205
+ event.preventDefault()
206
+ if (isCreatingSession) return
207
+ if (createBtnRef.current) {
208
+ createBtnRef.current.classList.add('active-scale')
209
+ setTimeout(() => {
210
+ createBtnRef.current?.classList.remove('active-scale')
211
+ }, 200)
212
+ }
213
+ void handleCreateSession()
214
+ }
215
+ })
216
+
217
+ return (
218
+ <div
219
+ className={`sidebar-container ${isSidebarCollapsed ? 'collapsed' : ''}`}
220
+ style={{
221
+ width: isSidebarCollapsed ? 0 : width,
222
+ minWidth: isSidebarCollapsed ? 0 : undefined,
223
+ transition: isResizing ? 'none' : 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
224
+ borderRight: isSidebarCollapsed ? 'none' : undefined
225
+ }}
226
+ >
227
+ <div
228
+ className='sidebar-content'
229
+ style={{
230
+ width,
231
+ transition: isResizing ? 'none' : 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
232
+ transform: isSidebarCollapsed ? `translateX(-${width}px)` : 'translateX(0)'
233
+ }}
234
+ >
235
+ <SidebarHeader
236
+ searchQuery={searchQuery}
237
+ onSearchChange={setSearchQuery}
238
+ isBatchMode={isBatchMode}
239
+ onToggleBatchMode={toggleBatchMode}
240
+ selectedCount={selectedIds.size}
241
+ totalCount={filteredSessions.length}
242
+ onSelectAll={handleSelectAll}
243
+ onBatchArchive={() => {
244
+ void handleBatchArchive()
245
+ }}
246
+ isCreatingSession={isCreatingSession}
247
+ onCreateSession={() => {
248
+ void handleCreateSession()
249
+ }}
250
+ />
251
+ <div className='sidebar-new-chat'>
252
+ <Tooltip title={isCreatingSession ? t('common.alreadyInNewChat') : undefined} placement='right'>
253
+ <Button
254
+ ref={createBtnRef}
255
+ className={`new-chat-btn ${isCreatingSession ? 'active' : ''}`}
256
+ type={isCreatingSession ? 'default' : 'primary'}
257
+ block
258
+ disabled={!!isCreatingSession}
259
+ onClick={() => {
260
+ void handleCreateSession()
261
+ }}
262
+ >
263
+ <span className='btn-content'>
264
+ <span className={`material-symbols-rounded ${isCreatingSession ? 'filled' : ''}`}>
265
+ {isCreatingSession ? 'chat_bubble' : 'send'}
266
+ </span>
267
+ <span>{isCreatingSession ? t('common.creatingChat') : t('common.newChat')}</span>
268
+ </span>
269
+ <span className='shortcut-tag'>
270
+ {shortcutLabel}
271
+ </span>
272
+ </Button>
273
+ </Tooltip>
274
+ </div>
275
+ <SessionList
276
+ sessions={filteredSessions}
277
+ activeId={activeId}
278
+ isBatchMode={isBatchMode}
279
+ selectedIds={selectedIds}
280
+ searchQuery={searchQuery}
281
+ onSelectSession={onSelectSession}
282
+ onArchiveSession={handleArchiveSession}
283
+ onDeleteSession={handleDeleteSession}
284
+ onStarSession={handleStarSession}
285
+ onUpdateTags={handleUpdateTags}
286
+ onToggleSelect={handleToggleSelect}
287
+ />
288
+ </div>
289
+ </div>
290
+ )
291
+ }