@huyooo/ai-chat-frontend-react 0.1.4 → 0.1.8
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/README.md +368 -0
- package/dist/index.css +2575 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +378 -135
- package/dist/index.js +3954 -1044
- package/dist/index.js.map +1 -1
- package/dist/style.css +48 -987
- package/package.json +7 -4
- package/src/adapter.ts +10 -70
- package/src/components/ChatPanel.tsx +373 -117
- package/src/components/common/ConfirmDialog.css +136 -0
- package/src/components/common/ConfirmDialog.tsx +91 -0
- package/src/components/common/CopyButton.css +22 -0
- package/src/components/common/CopyButton.tsx +46 -0
- package/src/components/common/IndexingSettings.css +207 -0
- package/src/components/common/IndexingSettings.tsx +398 -0
- package/src/components/common/SettingsPanel.css +256 -0
- package/src/components/common/SettingsPanel.tsx +120 -0
- package/src/components/common/Toast.css +50 -0
- package/src/components/common/Toast.tsx +38 -0
- package/src/components/common/ToggleSwitch.css +52 -0
- package/src/components/common/ToggleSwitch.tsx +20 -0
- package/src/components/header/ChatHeader.css +285 -0
- package/src/components/header/ChatHeader.tsx +376 -0
- package/src/components/input/AtFilePicker.css +147 -0
- package/src/components/input/AtFilePicker.tsx +519 -0
- package/src/components/input/ChatInput.css +204 -0
- package/src/components/input/ChatInput.tsx +506 -0
- package/src/components/input/DropdownSelector.css +159 -0
- package/src/components/input/DropdownSelector.tsx +195 -0
- package/src/components/input/ImagePreviewModal.css +124 -0
- package/src/components/input/ImagePreviewModal.tsx +118 -0
- package/src/components/input/at-views/AtBranchView.tsx +34 -0
- package/src/components/input/at-views/AtBrowserView.tsx +34 -0
- package/src/components/input/at-views/AtChatsView.tsx +34 -0
- package/src/components/input/at-views/AtDocsView.tsx +34 -0
- package/src/components/input/at-views/AtFilesView.tsx +168 -0
- package/src/components/input/at-views/AtTerminalsView.tsx +34 -0
- package/src/components/input/at-views/AtViewStyles.css +143 -0
- package/src/components/input/at-views/index.ts +9 -0
- package/src/components/message/ContentRenderer.css +9 -0
- package/src/components/message/ContentRenderer.tsx +63 -0
- package/src/components/message/MessageBubble.css +190 -0
- package/src/components/message/MessageBubble.tsx +231 -0
- package/src/components/message/PartsRenderer.css +4 -0
- package/src/components/message/PartsRenderer.tsx +114 -0
- package/src/components/message/ToolResultRenderer.tsx +21 -0
- package/src/components/message/WelcomeMessage.css +221 -0
- package/src/components/message/WelcomeMessage.tsx +93 -0
- package/src/components/message/blocks/CodeBlock.tsx +60 -0
- package/src/components/message/blocks/TextBlock.tsx +15 -0
- package/src/components/message/blocks/blocks.css +141 -0
- package/src/components/message/blocks/index.ts +6 -0
- package/src/components/message/parts/CollapsibleCard.css +78 -0
- package/src/components/message/parts/CollapsibleCard.tsx +77 -0
- package/src/components/message/parts/ErrorPart.css +9 -0
- package/src/components/message/parts/ErrorPart.tsx +40 -0
- package/src/components/message/parts/ImagePart.css +50 -0
- package/src/components/message/parts/ImagePart.tsx +54 -0
- package/src/components/message/parts/SearchPart.css +44 -0
- package/src/components/message/parts/SearchPart.tsx +63 -0
- package/src/components/message/parts/TextPart.css +10 -0
- package/src/components/message/parts/TextPart.tsx +20 -0
- package/src/components/message/parts/ThinkingPart.css +9 -0
- package/src/components/message/parts/ThinkingPart.tsx +48 -0
- package/src/components/message/parts/ToolCallPart.css +220 -0
- package/src/components/message/parts/ToolCallPart.tsx +285 -0
- package/src/components/message/parts/ToolResultPart.css +68 -0
- package/src/components/message/parts/ToolResultPart.tsx +96 -0
- package/src/components/message/parts/index.ts +11 -0
- package/src/components/message/tool-results/DefaultToolResult.tsx +26 -0
- package/src/components/message/tool-results/SearchResults.tsx +69 -0
- package/src/components/message/tool-results/WeatherCard.tsx +63 -0
- package/src/components/message/tool-results/index.ts +7 -0
- package/src/components/message/tool-results/tool-results.css +179 -0
- package/src/components/message/welcome-types.ts +46 -0
- package/src/context/AutoRunConfigContext.tsx +13 -0
- package/src/context/ChatAdapterContext.tsx +8 -0
- package/src/context/ChatInputContext.tsx +40 -0
- package/src/context/RenderersContext.tsx +41 -0
- package/src/hooks/useChat.ts +855 -237
- package/src/hooks/useImageUpload.ts +253 -0
- package/src/index.ts +99 -42
- package/src/styles.css +48 -987
- package/src/types/index.ts +172 -103
- package/src/utils/fileIcon.ts +49 -0
- package/src/components/ChatInput.tsx +0 -368
- package/src/components/chat/messages/ExecutionSteps.tsx +0 -234
- package/src/components/chat/messages/MessageBubble.tsx +0 -130
- package/src/components/chat/ui/ChatHeader.tsx +0 -301
- package/src/components/chat/ui/WelcomeMessage.tsx +0 -107
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatHeader Component
|
|
3
|
+
* 与 Vue 版本 ChatHeader.vue 保持一致
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useRef, useEffect, useCallback, type FC } from 'react'
|
|
7
|
+
import './ChatHeader.css'
|
|
8
|
+
import { Icon } from '@iconify/react'
|
|
9
|
+
import type { SessionRecord } from '../../types'
|
|
10
|
+
|
|
11
|
+
interface ChatHeaderProps {
|
|
12
|
+
/** 当前会话列表 */
|
|
13
|
+
sessions: SessionRecord[]
|
|
14
|
+
/** 当前会话 ID */
|
|
15
|
+
currentSessionId?: string | null
|
|
16
|
+
/** 是否显示关闭按钮 */
|
|
17
|
+
showClose?: boolean
|
|
18
|
+
/** 创建新会话 */
|
|
19
|
+
onNewSession?: () => void
|
|
20
|
+
/** 切换会话 */
|
|
21
|
+
onSwitchSession?: (sessionId: string) => void
|
|
22
|
+
/** 删除会话 */
|
|
23
|
+
onDeleteSession?: (sessionId: string) => void
|
|
24
|
+
/** 隐藏/显示会话(在 tab 栏关闭但不删除) */
|
|
25
|
+
onHideSession?: (sessionId: string, hidden: boolean) => void
|
|
26
|
+
/** 关闭面板 */
|
|
27
|
+
onClose?: () => void
|
|
28
|
+
/** 清空所有对话 */
|
|
29
|
+
onClearAll?: () => void
|
|
30
|
+
/** 关闭其他对话 */
|
|
31
|
+
onCloseOthers?: () => void
|
|
32
|
+
/** 导出对话 */
|
|
33
|
+
onExport?: () => void
|
|
34
|
+
/** 复制请求 ID */
|
|
35
|
+
onCopyId?: () => void
|
|
36
|
+
/** 反馈 */
|
|
37
|
+
onFeedback?: () => void
|
|
38
|
+
/** Agent 设置 */
|
|
39
|
+
onSettings?: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 获取日期分组标签 */
|
|
43
|
+
function getDateGroupLabel(date: Date | string): string {
|
|
44
|
+
const d = new Date(date)
|
|
45
|
+
const now = new Date()
|
|
46
|
+
|
|
47
|
+
// 重置时间部分,只比较日期
|
|
48
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
49
|
+
const target = new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
|
50
|
+
const diff = today.getTime() - target.getTime()
|
|
51
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
|
52
|
+
|
|
53
|
+
if (days === 0) return '今天'
|
|
54
|
+
if (days === 1) return '昨天'
|
|
55
|
+
|
|
56
|
+
// 其他显示具体日期
|
|
57
|
+
const month = d.getMonth() + 1
|
|
58
|
+
const day = d.getDate()
|
|
59
|
+
return `${month}月${day}日`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 格式化时间(HH:mm) */
|
|
63
|
+
function formatTime(date: Date | string): string {
|
|
64
|
+
const d = new Date(date)
|
|
65
|
+
const hours = String(d.getHours()).padStart(2, '0')
|
|
66
|
+
const minutes = String(d.getMinutes()).padStart(2, '0')
|
|
67
|
+
return `${hours}:${minutes}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 按日期分组会话 */
|
|
71
|
+
interface SessionGroup {
|
|
72
|
+
label: string
|
|
73
|
+
sessions: SessionRecord[]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function groupSessionsByDate(sessions: SessionRecord[]): SessionGroup[] {
|
|
77
|
+
const groups = new Map<string, SessionRecord[]>()
|
|
78
|
+
|
|
79
|
+
for (const session of sessions) {
|
|
80
|
+
const label = getDateGroupLabel(session.updatedAt)
|
|
81
|
+
const group = groups.get(label)
|
|
82
|
+
if (group) {
|
|
83
|
+
group.push(session)
|
|
84
|
+
} else {
|
|
85
|
+
groups.set(label, [session])
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Array.from(groups.entries()).map(([label, groupSessions]) => ({
|
|
90
|
+
label,
|
|
91
|
+
sessions: groupSessions,
|
|
92
|
+
}))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 获取显示标题 */
|
|
96
|
+
function getDisplayTitle(title: string): string {
|
|
97
|
+
return title === '新对话' ? 'New Chat' : title
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const ChatHeader: FC<ChatHeaderProps> = ({
|
|
101
|
+
sessions,
|
|
102
|
+
currentSessionId,
|
|
103
|
+
showClose = false,
|
|
104
|
+
onNewSession,
|
|
105
|
+
onSwitchSession,
|
|
106
|
+
onDeleteSession,
|
|
107
|
+
onHideSession,
|
|
108
|
+
onClose,
|
|
109
|
+
onClearAll,
|
|
110
|
+
onCloseOthers,
|
|
111
|
+
onExport,
|
|
112
|
+
onCopyId,
|
|
113
|
+
onFeedback,
|
|
114
|
+
onSettings,
|
|
115
|
+
}) => {
|
|
116
|
+
const [historyOpen, setHistoryOpen] = useState(false)
|
|
117
|
+
const [moreMenuOpen, setMoreMenuOpen] = useState(false)
|
|
118
|
+
|
|
119
|
+
const historyRef = useRef<HTMLDivElement>(null)
|
|
120
|
+
const moreMenuRef = useRef<HTMLDivElement>(null)
|
|
121
|
+
|
|
122
|
+
// 可见的会话(使用 sessions 的 hidden 字段)
|
|
123
|
+
const visibleSessions = sessions.filter((s) => !s.hidden)
|
|
124
|
+
|
|
125
|
+
// 点击外部关闭菜单
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
128
|
+
const target = event.target as HTMLElement
|
|
129
|
+
if (historyRef.current && !historyRef.current.contains(target)) {
|
|
130
|
+
setHistoryOpen(false)
|
|
131
|
+
}
|
|
132
|
+
if (moreMenuRef.current && !moreMenuRef.current.contains(target)) {
|
|
133
|
+
setMoreMenuOpen(false)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
document.addEventListener('click', handleClickOutside)
|
|
138
|
+
return () => document.removeEventListener('click', handleClickOutside)
|
|
139
|
+
}, [])
|
|
140
|
+
|
|
141
|
+
// 切换历史面板
|
|
142
|
+
const toggleHistory = useCallback(() => {
|
|
143
|
+
setHistoryOpen((prev) => !prev)
|
|
144
|
+
setMoreMenuOpen(false)
|
|
145
|
+
}, [])
|
|
146
|
+
|
|
147
|
+
// 切换更多菜单
|
|
148
|
+
const toggleMore = useCallback(() => {
|
|
149
|
+
setMoreMenuOpen((prev) => !prev)
|
|
150
|
+
setHistoryOpen(false)
|
|
151
|
+
}, [])
|
|
152
|
+
|
|
153
|
+
// 从历史新建
|
|
154
|
+
const handleNewFromHistory = useCallback(() => {
|
|
155
|
+
onNewSession?.()
|
|
156
|
+
setHistoryOpen(false)
|
|
157
|
+
}, [onNewSession])
|
|
158
|
+
|
|
159
|
+
// 选择历史
|
|
160
|
+
const handleSelectHistory = useCallback(
|
|
161
|
+
(sessionId: string) => {
|
|
162
|
+
const session = sessions.find((s) => s.id === sessionId)
|
|
163
|
+
// 如果被隐藏了,恢复显示
|
|
164
|
+
if (session?.hidden) {
|
|
165
|
+
onHideSession?.(sessionId, false)
|
|
166
|
+
}
|
|
167
|
+
onSwitchSession?.(sessionId)
|
|
168
|
+
setHistoryOpen(false)
|
|
169
|
+
},
|
|
170
|
+
[sessions, onSwitchSession, onHideSession]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// 隐藏 tab(触发回调由父组件持久化到数据库)
|
|
174
|
+
const handleHideTab = useCallback(
|
|
175
|
+
(sessionId: string, e: React.MouseEvent) => {
|
|
176
|
+
e.stopPropagation()
|
|
177
|
+
onHideSession?.(sessionId, true)
|
|
178
|
+
if (sessionId === currentSessionId) {
|
|
179
|
+
const remaining = sessions.filter((s) => s.id !== sessionId && !s.hidden)
|
|
180
|
+
if (remaining.length > 0) {
|
|
181
|
+
onSwitchSession?.(remaining[0].id)
|
|
182
|
+
} else {
|
|
183
|
+
// 最后一个 tab 被关闭,创建新会话
|
|
184
|
+
onNewSession?.()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[currentSessionId, sessions, onSwitchSession, onNewSession, onHideSession]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// 删除会话
|
|
192
|
+
const handleDeleteSession = (sessionId: string, e: React.MouseEvent) => {
|
|
193
|
+
e.stopPropagation()
|
|
194
|
+
onDeleteSession?.(sessionId)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 菜单操作
|
|
198
|
+
const handleMenuAction = (action: string) => {
|
|
199
|
+
setMoreMenuOpen(false)
|
|
200
|
+
switch (action) {
|
|
201
|
+
case 'clear-all':
|
|
202
|
+
onClearAll?.()
|
|
203
|
+
break
|
|
204
|
+
case 'close-others':
|
|
205
|
+
onCloseOthers?.()
|
|
206
|
+
break
|
|
207
|
+
case 'export':
|
|
208
|
+
onExport?.()
|
|
209
|
+
break
|
|
210
|
+
case 'copy-id':
|
|
211
|
+
onCopyId?.()
|
|
212
|
+
break
|
|
213
|
+
case 'feedback':
|
|
214
|
+
onFeedback?.()
|
|
215
|
+
break
|
|
216
|
+
case 'settings':
|
|
217
|
+
onSettings?.()
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div className="chat-header">
|
|
224
|
+
{/* 左侧:Tabs 可滚动区域 */}
|
|
225
|
+
<div className="tabs-container">
|
|
226
|
+
{visibleSessions.length === 0 ? (
|
|
227
|
+
<span className="tab-item active">New Chat</span>
|
|
228
|
+
) : (
|
|
229
|
+
visibleSessions.map((session) => {
|
|
230
|
+
const isActive = session.id === currentSessionId
|
|
231
|
+
return (
|
|
232
|
+
<div
|
|
233
|
+
key={session.id}
|
|
234
|
+
className={`tab-item${isActive ? ' active' : ''}`}
|
|
235
|
+
onClick={() => onSwitchSession?.(session.id)}
|
|
236
|
+
title={session.title}
|
|
237
|
+
>
|
|
238
|
+
<span className="tab-title">{getDisplayTitle(session.title)}</span>
|
|
239
|
+
<button
|
|
240
|
+
className="tab-close"
|
|
241
|
+
onClick={(e) => handleHideTab(session.id, e)}
|
|
242
|
+
title="关闭标签"
|
|
243
|
+
>
|
|
244
|
+
<Icon icon="lucide:x" width={18} />
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
})
|
|
249
|
+
)}
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
{/* 右侧:操作按钮 */}
|
|
253
|
+
<div className="header-actions">
|
|
254
|
+
{/* 新建会话 */}
|
|
255
|
+
<button className="icon-btn" onClick={onNewSession} title="新建对话">
|
|
256
|
+
<Icon icon="lucide:plus" width={18} />
|
|
257
|
+
</button>
|
|
258
|
+
|
|
259
|
+
{/* 历史记录 */}
|
|
260
|
+
<div ref={historyRef} className="dropdown-container">
|
|
261
|
+
<button
|
|
262
|
+
className={`icon-btn${historyOpen ? ' active' : ''}`}
|
|
263
|
+
onClick={(e) => {
|
|
264
|
+
e.stopPropagation()
|
|
265
|
+
toggleHistory()
|
|
266
|
+
}}
|
|
267
|
+
title="历史记录"
|
|
268
|
+
>
|
|
269
|
+
<Icon icon="lucide:clock" width={18} />
|
|
270
|
+
</button>
|
|
271
|
+
|
|
272
|
+
{/* 历史记录面板 */}
|
|
273
|
+
{historyOpen && (
|
|
274
|
+
<div className="dropdown-panel history-panel">
|
|
275
|
+
<div className="panel-header">
|
|
276
|
+
<span>历史记录</span>
|
|
277
|
+
<button className="icon-btn small" title="新建对话" onClick={handleNewFromHistory}>
|
|
278
|
+
<Icon icon="lucide:plus" width={18} />
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="panel-content chat-scrollbar">
|
|
282
|
+
{sessions.length === 0 ? (
|
|
283
|
+
<div className="empty-state">暂无历史对话</div>
|
|
284
|
+
) : (
|
|
285
|
+
groupSessionsByDate(sessions).map((group) => (
|
|
286
|
+
<div key={group.label}>
|
|
287
|
+
<div className="history-group-label">{group.label}</div>
|
|
288
|
+
{group.sessions.map((session) => {
|
|
289
|
+
const isCurrent = session.id === currentSessionId
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
key={session.id}
|
|
293
|
+
className={`history-item${isCurrent ? ' active' : ''}`}
|
|
294
|
+
onClick={() => handleSelectHistory(session.id)}
|
|
295
|
+
>
|
|
296
|
+
<span className="history-title">{session.title}</span>
|
|
297
|
+
<span className="history-time">{formatTime(session.updatedAt)}</span>
|
|
298
|
+
<button
|
|
299
|
+
className="history-action-btn delete"
|
|
300
|
+
title="删除"
|
|
301
|
+
onClick={(e) => handleDeleteSession(session.id, e)}
|
|
302
|
+
>
|
|
303
|
+
<Icon icon="lucide:trash-2" width={18} />
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
)
|
|
307
|
+
})}
|
|
308
|
+
</div>
|
|
309
|
+
))
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
{/* 更多选项 */}
|
|
317
|
+
<div ref={moreMenuRef} className="dropdown-container">
|
|
318
|
+
<button
|
|
319
|
+
className={`icon-btn${moreMenuOpen ? ' active' : ''}`}
|
|
320
|
+
onClick={(e) => {
|
|
321
|
+
e.stopPropagation()
|
|
322
|
+
toggleMore()
|
|
323
|
+
}}
|
|
324
|
+
title="更多选项"
|
|
325
|
+
>
|
|
326
|
+
<Icon icon="lucide:more-horizontal" width={18} />
|
|
327
|
+
</button>
|
|
328
|
+
|
|
329
|
+
{/* 更多选项菜单 */}
|
|
330
|
+
{moreMenuOpen && (
|
|
331
|
+
<div className="dropdown-panel more-panel">
|
|
332
|
+
<button className="menu-item" onClick={() => handleMenuAction('clear-all')}>
|
|
333
|
+
<Icon icon="lucide:trash-2" width={18} />
|
|
334
|
+
<span>清空所有对话</span>
|
|
335
|
+
</button>
|
|
336
|
+
<button className="menu-item" onClick={() => handleMenuAction('close-others')}>
|
|
337
|
+
<Icon icon="lucide:x-circle" width={18} />
|
|
338
|
+
<span>关闭其他对话</span>
|
|
339
|
+
</button>
|
|
340
|
+
<div className="menu-divider" />
|
|
341
|
+
<button className="menu-item" onClick={() => handleMenuAction('export')}>
|
|
342
|
+
<Icon icon="lucide:download" width={18} />
|
|
343
|
+
<span>导出对话</span>
|
|
344
|
+
</button>
|
|
345
|
+
<button className="menu-item" onClick={() => handleMenuAction('copy-id')}>
|
|
346
|
+
<Icon icon="lucide:copy" width={18} />
|
|
347
|
+
<span>复制请求 ID</span>
|
|
348
|
+
</button>
|
|
349
|
+
<div className="menu-divider" />
|
|
350
|
+
<button className="menu-item" onClick={() => handleMenuAction('feedback')}>
|
|
351
|
+
<Icon icon="lucide:message-square" width={18} />
|
|
352
|
+
<span>反馈</span>
|
|
353
|
+
</button>
|
|
354
|
+
<button className="menu-item" onClick={() => handleMenuAction('settings')}>
|
|
355
|
+
<Icon icon="lucide:settings" width={18} />
|
|
356
|
+
<span>Agent 设置</span>
|
|
357
|
+
</button>
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
{/* 关闭按钮 */}
|
|
363
|
+
{showClose && (
|
|
364
|
+
<button className="icon-btn" onClick={onClose} title="关闭">
|
|
365
|
+
<Icon icon="lucide:x" width={18} />
|
|
366
|
+
</button>
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
{/* 设置按钮 */}
|
|
370
|
+
<button className="icon-btn" onClick={onSettings} title="设置">
|
|
371
|
+
<Icon icon="lucide:settings" width={18} />
|
|
372
|
+
</button>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
)
|
|
376
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
.at-picker-dropdown {
|
|
2
|
+
position: fixed;
|
|
3
|
+
width: 332px;
|
|
4
|
+
background: var(--chat-dropdown-bg, #252526);
|
|
5
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
6
|
+
border-radius: 10px;
|
|
7
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
8
|
+
z-index: 99999;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.at-picker-header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
padding: 10px 12px;
|
|
19
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.at-picker-back {
|
|
23
|
+
width: 24px;
|
|
24
|
+
height: 24px;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
background: transparent;
|
|
29
|
+
border: none;
|
|
30
|
+
border-radius: 4px;
|
|
31
|
+
color: #888;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.at-picker-back:hover {
|
|
37
|
+
background: rgba(255, 255, 255, 0.08);
|
|
38
|
+
color: #ccc;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.at-picker-search-icon {
|
|
42
|
+
color: #666;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.at-picker-search {
|
|
47
|
+
flex: 1;
|
|
48
|
+
background: transparent;
|
|
49
|
+
border: none;
|
|
50
|
+
outline: none;
|
|
51
|
+
color: #ddd;
|
|
52
|
+
font-size: 13px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.at-picker-body {
|
|
56
|
+
padding: 6px;
|
|
57
|
+
flex: 1;
|
|
58
|
+
min-height: 0;
|
|
59
|
+
overflow-y: auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* 滚动期间禁用鼠标悬停事件 */
|
|
63
|
+
.at-picker-body.is-scrolling .at-picker-item,
|
|
64
|
+
.at-picker-body.is-scrolling .at-view-item {
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.at-picker-section {
|
|
69
|
+
margin-bottom: 2px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.at-picker-recent {
|
|
73
|
+
padding-bottom: 6px;
|
|
74
|
+
margin-bottom: 6px;
|
|
75
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.at-picker-list {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 1px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.at-picker-item {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 8px;
|
|
88
|
+
text-align: left;
|
|
89
|
+
padding: 7px 10px;
|
|
90
|
+
border-radius: 6px;
|
|
91
|
+
border: 1px solid transparent;
|
|
92
|
+
background: transparent;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
color: #ccc;
|
|
95
|
+
width: 100%;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.at-picker-item:hover {
|
|
99
|
+
background: rgba(255, 255, 255, 0.06);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.at-picker-item.active {
|
|
103
|
+
background: rgba(59, 130, 246, 0.15);
|
|
104
|
+
border-color: rgba(59, 130, 246, 0.3);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.at-picker-item-icon {
|
|
108
|
+
color: #999;
|
|
109
|
+
flex-shrink: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.at-picker-item-name {
|
|
113
|
+
font-size: 13px;
|
|
114
|
+
color: #ddd;
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
max-width: 160px;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.at-picker-item-path {
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
color: #555;
|
|
125
|
+
min-width: 0;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
text-overflow: ellipsis;
|
|
128
|
+
white-space: nowrap;
|
|
129
|
+
flex: 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.at-picker-category .at-picker-item-name {
|
|
133
|
+
flex: 1;
|
|
134
|
+
max-width: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.at-picker-chevron {
|
|
138
|
+
color: #555;
|
|
139
|
+
flex-shrink: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.at-picker-footer {
|
|
143
|
+
padding: 8px 12px;
|
|
144
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
145
|
+
color: #555;
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
}
|