@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
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MessageBubble Component
|
|
3
|
-
* 与 Vue 版本 MessageBubble.vue 保持一致
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { type FC, type ReactNode } from 'react'
|
|
7
|
-
import { Copy, Check, RefreshCw } from 'lucide-react'
|
|
8
|
-
import { ExecutionSteps } from './ExecutionSteps'
|
|
9
|
-
import { ChatInput } from '../../ChatInput'
|
|
10
|
-
import type { SearchResult, ToolCall } from '../../../types'
|
|
11
|
-
|
|
12
|
-
interface MessageBubbleProps {
|
|
13
|
-
role: 'user' | 'assistant'
|
|
14
|
-
content: string
|
|
15
|
-
images?: string[]
|
|
16
|
-
thinking?: string
|
|
17
|
-
thinkingComplete?: boolean
|
|
18
|
-
thinkingDuration?: number
|
|
19
|
-
searchResults?: SearchResult[]
|
|
20
|
-
searching?: boolean
|
|
21
|
-
toolCalls?: ToolCall[]
|
|
22
|
-
copied?: boolean
|
|
23
|
-
loading?: boolean
|
|
24
|
-
onCopy?: () => void
|
|
25
|
-
onRegenerate?: () => void
|
|
26
|
-
/** 编辑用户消息后重新发送 */
|
|
27
|
-
onSend?: (text: string) => void
|
|
28
|
-
/** 自定义 Markdown 渲染器 */
|
|
29
|
-
renderMarkdown?: (content: string) => ReactNode
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** 默认 Markdown 渲染(简单处理) */
|
|
33
|
-
function defaultRenderMarkdown(content: string): ReactNode {
|
|
34
|
-
// 简单的 Markdown 处理:代码块
|
|
35
|
-
const parts = content.split(/(```[\s\S]*?```)/g)
|
|
36
|
-
|
|
37
|
-
return parts.map((part, i) => {
|
|
38
|
-
if (part.startsWith('```') && part.endsWith('```')) {
|
|
39
|
-
const code = part.slice(3, -3)
|
|
40
|
-
const firstLine = code.indexOf('\n')
|
|
41
|
-
const lang = firstLine > 0 ? code.slice(0, firstLine).trim() : ''
|
|
42
|
-
const codeContent = firstLine > 0 ? code.slice(firstLine + 1) : code
|
|
43
|
-
return (
|
|
44
|
-
<pre key={i}>
|
|
45
|
-
<code>{codeContent}</code>
|
|
46
|
-
</pre>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
// 处理行内代码
|
|
50
|
-
const inlineParts = part.split(/(`[^`]+`)/g)
|
|
51
|
-
return (
|
|
52
|
-
<span key={i}>
|
|
53
|
-
{inlineParts.map((p, j) => {
|
|
54
|
-
if (p.startsWith('`') && p.endsWith('`')) {
|
|
55
|
-
return <code key={j}>{p.slice(1, -1)}</code>
|
|
56
|
-
}
|
|
57
|
-
return p
|
|
58
|
-
})}
|
|
59
|
-
</span>
|
|
60
|
-
)
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const MessageBubble: FC<MessageBubbleProps> = ({
|
|
65
|
-
role,
|
|
66
|
-
content,
|
|
67
|
-
images,
|
|
68
|
-
thinking,
|
|
69
|
-
thinkingComplete = true,
|
|
70
|
-
thinkingDuration,
|
|
71
|
-
searchResults,
|
|
72
|
-
searching,
|
|
73
|
-
toolCalls,
|
|
74
|
-
copied,
|
|
75
|
-
loading,
|
|
76
|
-
onCopy,
|
|
77
|
-
onRegenerate,
|
|
78
|
-
onSend,
|
|
79
|
-
renderMarkdown = defaultRenderMarkdown,
|
|
80
|
-
}) => {
|
|
81
|
-
const isUser = role === 'user'
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div className="message-bubble">
|
|
85
|
-
{/* 用户消息 - 复用 ChatInput 组件 */}
|
|
86
|
-
{isUser ? (
|
|
87
|
-
<ChatInput
|
|
88
|
-
variant="message"
|
|
89
|
-
value={content}
|
|
90
|
-
selectedImages={images}
|
|
91
|
-
onSend={onSend}
|
|
92
|
-
/>
|
|
93
|
-
) : (
|
|
94
|
-
/* AI 消息 */
|
|
95
|
-
<div className="assistant-message">
|
|
96
|
-
{/* 执行步骤列表 */}
|
|
97
|
-
<ExecutionSteps
|
|
98
|
-
loading={loading}
|
|
99
|
-
hasContent={!!content}
|
|
100
|
-
thinking={thinking}
|
|
101
|
-
thinkingComplete={thinkingComplete}
|
|
102
|
-
thinkingDuration={thinkingDuration}
|
|
103
|
-
searching={searching}
|
|
104
|
-
searchResults={searchResults}
|
|
105
|
-
toolCalls={toolCalls}
|
|
106
|
-
/>
|
|
107
|
-
|
|
108
|
-
{/* 消息内容 */}
|
|
109
|
-
{content && (
|
|
110
|
-
<div className="message-content">
|
|
111
|
-
{renderMarkdown(content)}
|
|
112
|
-
</div>
|
|
113
|
-
)}
|
|
114
|
-
|
|
115
|
-
{/* 操作按钮 */}
|
|
116
|
-
{content && !loading && (
|
|
117
|
-
<div className="message-actions">
|
|
118
|
-
<button className={`action-btn${copied ? ' copied' : ''}`} onClick={onCopy} title="复制">
|
|
119
|
-
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
120
|
-
</button>
|
|
121
|
-
<button className="action-btn" onClick={onRegenerate} title="重新生成">
|
|
122
|
-
<RefreshCw size={14} />
|
|
123
|
-
</button>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
128
|
-
</div>
|
|
129
|
-
)
|
|
130
|
-
}
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChatHeader Component
|
|
3
|
-
* 与 Vue 版本 ChatHeader.vue 保持一致
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useRef, useEffect, useCallback, type FC } from 'react'
|
|
7
|
-
import { Plus, Clock, MoreHorizontal, X, MessageSquare, Pencil, Trash2 } from 'lucide-react'
|
|
8
|
-
import type { SessionRecord } from '../../../types'
|
|
9
|
-
|
|
10
|
-
interface ChatHeaderProps {
|
|
11
|
-
/** 当前会话列表 */
|
|
12
|
-
sessions: SessionRecord[]
|
|
13
|
-
/** 当前会话 ID */
|
|
14
|
-
currentSessionId?: string | null
|
|
15
|
-
/** 是否显示关闭按钮 */
|
|
16
|
-
showClose?: boolean
|
|
17
|
-
/** 创建新会话 */
|
|
18
|
-
onNewSession?: () => void
|
|
19
|
-
/** 切换会话 */
|
|
20
|
-
onSwitchSession?: (sessionId: string) => void
|
|
21
|
-
/** 删除会话 */
|
|
22
|
-
onDeleteSession?: (sessionId: string) => void
|
|
23
|
-
/** 关闭面板 */
|
|
24
|
-
onClose?: () => void
|
|
25
|
-
/** 清空所有对话 */
|
|
26
|
-
onClearAll?: () => void
|
|
27
|
-
/** 关闭其他对话 */
|
|
28
|
-
onCloseOthers?: () => void
|
|
29
|
-
/** 导出对话 */
|
|
30
|
-
onExport?: () => void
|
|
31
|
-
/** 复制请求 ID */
|
|
32
|
-
onCopyId?: () => void
|
|
33
|
-
/** 反馈 */
|
|
34
|
-
onFeedback?: () => void
|
|
35
|
-
/** Agent 设置 */
|
|
36
|
-
onSettings?: () => void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** 格式化时间 */
|
|
40
|
-
function formatTime(date: Date | string | undefined): string {
|
|
41
|
-
if (!date) return ''
|
|
42
|
-
const d = new Date(date)
|
|
43
|
-
const now = new Date()
|
|
44
|
-
const diff = now.getTime() - d.getTime()
|
|
45
|
-
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
|
46
|
-
|
|
47
|
-
if (days === 0) {
|
|
48
|
-
return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
|
49
|
-
} else if (days === 1) {
|
|
50
|
-
return '昨天'
|
|
51
|
-
} else if (days < 7) {
|
|
52
|
-
return `${days}天前`
|
|
53
|
-
} else {
|
|
54
|
-
return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const ChatHeader: FC<ChatHeaderProps> = ({
|
|
59
|
-
sessions,
|
|
60
|
-
currentSessionId,
|
|
61
|
-
showClose = false,
|
|
62
|
-
onNewSession,
|
|
63
|
-
onSwitchSession,
|
|
64
|
-
onDeleteSession,
|
|
65
|
-
onClose,
|
|
66
|
-
onClearAll,
|
|
67
|
-
onCloseOthers,
|
|
68
|
-
onExport,
|
|
69
|
-
onCopyId,
|
|
70
|
-
onFeedback,
|
|
71
|
-
onSettings,
|
|
72
|
-
}) => {
|
|
73
|
-
const [historyOpen, setHistoryOpen] = useState(false)
|
|
74
|
-
const [moreMenuOpen, setMoreMenuOpen] = useState(false)
|
|
75
|
-
const [hiddenTabs, setHiddenTabs] = useState<Set<string>>(new Set())
|
|
76
|
-
|
|
77
|
-
const historyRef = useRef<HTMLDivElement>(null)
|
|
78
|
-
const moreMenuRef = useRef<HTMLDivElement>(null)
|
|
79
|
-
|
|
80
|
-
// 可见的会话
|
|
81
|
-
const visibleSessions = sessions.filter((s) => !hiddenTabs.has(s.id))
|
|
82
|
-
|
|
83
|
-
// 点击外部关闭菜单
|
|
84
|
-
useEffect(() => {
|
|
85
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
86
|
-
const target = event.target as HTMLElement
|
|
87
|
-
if (historyRef.current && !historyRef.current.contains(target)) {
|
|
88
|
-
setHistoryOpen(false)
|
|
89
|
-
}
|
|
90
|
-
if (moreMenuRef.current && !moreMenuRef.current.contains(target)) {
|
|
91
|
-
setMoreMenuOpen(false)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
document.addEventListener('click', handleClickOutside)
|
|
96
|
-
return () => document.removeEventListener('click', handleClickOutside)
|
|
97
|
-
}, [])
|
|
98
|
-
|
|
99
|
-
// 切换会话
|
|
100
|
-
const handleSwitchSession = useCallback(
|
|
101
|
-
(sessionId: string) => {
|
|
102
|
-
onSwitchSession?.(sessionId)
|
|
103
|
-
setHistoryOpen(false)
|
|
104
|
-
},
|
|
105
|
-
[onSwitchSession]
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
// 隐藏 tab
|
|
109
|
-
const handleHideTab = useCallback(
|
|
110
|
-
(sessionId: string, e: React.MouseEvent) => {
|
|
111
|
-
e.stopPropagation()
|
|
112
|
-
setHiddenTabs((prev) => new Set([...prev, sessionId]))
|
|
113
|
-
if (sessionId === currentSessionId) {
|
|
114
|
-
const remaining = sessions.filter((s) => s.id !== sessionId && !hiddenTabs.has(s.id))
|
|
115
|
-
if (remaining.length > 0) {
|
|
116
|
-
onSwitchSession?.(remaining[0].id)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
[currentSessionId, sessions, hiddenTabs, onSwitchSession]
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
// 删除会话
|
|
124
|
-
const handleDeleteSession = (sessionId: string, e: React.MouseEvent) => {
|
|
125
|
-
e.stopPropagation()
|
|
126
|
-
if (window.confirm('确定要删除这个对话吗?')) {
|
|
127
|
-
onDeleteSession?.(sessionId)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 菜单项点击
|
|
132
|
-
const handleMenuClick = (callback?: () => void) => {
|
|
133
|
-
callback?.()
|
|
134
|
-
setMoreMenuOpen(false)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div className="chat-header">
|
|
139
|
-
{/* 左侧:Tabs */}
|
|
140
|
-
<div className="chat-tabs">
|
|
141
|
-
{visibleSessions.length === 0 ? (
|
|
142
|
-
<span className="chat-tab active">
|
|
143
|
-
<span className="chat-tab-title">New Chat</span>
|
|
144
|
-
</span>
|
|
145
|
-
) : (
|
|
146
|
-
visibleSessions.map((session) => {
|
|
147
|
-
const title = session.title === '新对话' ? 'New Chat' : session.title
|
|
148
|
-
const isActive = session.id === currentSessionId
|
|
149
|
-
return (
|
|
150
|
-
<div
|
|
151
|
-
key={session.id}
|
|
152
|
-
className={`chat-tab${isActive ? ' active' : ''}`}
|
|
153
|
-
onClick={() => handleSwitchSession(session.id)}
|
|
154
|
-
title={session.title}
|
|
155
|
-
>
|
|
156
|
-
<span className="chat-tab-title">{title}</span>
|
|
157
|
-
<span
|
|
158
|
-
className="chat-tab-close"
|
|
159
|
-
onClick={(e) => handleHideTab(session.id, e)}
|
|
160
|
-
title="关闭标签"
|
|
161
|
-
>
|
|
162
|
-
<X size={12} />
|
|
163
|
-
</span>
|
|
164
|
-
</div>
|
|
165
|
-
)
|
|
166
|
-
})
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{/* 右侧:操作按钮 */}
|
|
171
|
-
<div className="chat-header-actions">
|
|
172
|
-
{/* 新建会话 */}
|
|
173
|
-
<button className="header-btn" onClick={onNewSession} title="新建对话">
|
|
174
|
-
<Plus size={14} />
|
|
175
|
-
</button>
|
|
176
|
-
|
|
177
|
-
{/* 历史记录 */}
|
|
178
|
-
<div ref={historyRef} style={{ position: 'relative' }}>
|
|
179
|
-
<button
|
|
180
|
-
className={`header-btn${historyOpen ? ' active' : ''}`}
|
|
181
|
-
onClick={(e) => {
|
|
182
|
-
e.stopPropagation()
|
|
183
|
-
setHistoryOpen(!historyOpen)
|
|
184
|
-
setMoreMenuOpen(false)
|
|
185
|
-
}}
|
|
186
|
-
title="历史记录"
|
|
187
|
-
>
|
|
188
|
-
<Clock size={14} />
|
|
189
|
-
</button>
|
|
190
|
-
|
|
191
|
-
{/* 历史记录面板 */}
|
|
192
|
-
{historyOpen && (
|
|
193
|
-
<div className="history-panel">
|
|
194
|
-
{sessions.length === 0 ? (
|
|
195
|
-
<div className="history-empty">暂无历史对话</div>
|
|
196
|
-
) : (
|
|
197
|
-
sessions.map((session) => {
|
|
198
|
-
const isCurrent = session.id === currentSessionId
|
|
199
|
-
return (
|
|
200
|
-
<div
|
|
201
|
-
key={session.id}
|
|
202
|
-
className={`history-item${isCurrent ? ' active' : ''}`}
|
|
203
|
-
>
|
|
204
|
-
<button
|
|
205
|
-
className="history-item-content"
|
|
206
|
-
onClick={() => handleSwitchSession(session.id)}
|
|
207
|
-
>
|
|
208
|
-
<MessageSquare size={12} />
|
|
209
|
-
<span className="history-item-title">
|
|
210
|
-
{session.title === '新对话' ? 'New Chat' : session.title}
|
|
211
|
-
</span>
|
|
212
|
-
<span className="history-item-time">
|
|
213
|
-
{isCurrent ? 'Current' : formatTime(session.updatedAt)}
|
|
214
|
-
</span>
|
|
215
|
-
</button>
|
|
216
|
-
<div className="history-item-actions">
|
|
217
|
-
<button className="history-action-btn" title="编辑">
|
|
218
|
-
<Pencil size={10} />
|
|
219
|
-
</button>
|
|
220
|
-
<button
|
|
221
|
-
className="history-action-btn delete"
|
|
222
|
-
title="删除"
|
|
223
|
-
onClick={(e) => handleDeleteSession(session.id, e)}
|
|
224
|
-
>
|
|
225
|
-
<Trash2 size={10} />
|
|
226
|
-
</button>
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
)
|
|
230
|
-
})
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
)}
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
{/* 更多选项 */}
|
|
237
|
-
<div ref={moreMenuRef} style={{ position: 'relative' }}>
|
|
238
|
-
<button
|
|
239
|
-
className={`header-btn${moreMenuOpen ? ' active' : ''}`}
|
|
240
|
-
onClick={(e) => {
|
|
241
|
-
e.stopPropagation()
|
|
242
|
-
setMoreMenuOpen(!moreMenuOpen)
|
|
243
|
-
setHistoryOpen(false)
|
|
244
|
-
}}
|
|
245
|
-
title="更多选项"
|
|
246
|
-
>
|
|
247
|
-
<MoreHorizontal size={14} />
|
|
248
|
-
</button>
|
|
249
|
-
|
|
250
|
-
{/* 更多选项菜单 */}
|
|
251
|
-
{moreMenuOpen && (
|
|
252
|
-
<div className="more-menu">
|
|
253
|
-
{showClose && (
|
|
254
|
-
<button className="menu-item" onClick={() => handleMenuClick(onClose)}>
|
|
255
|
-
<span>关闭对话</span>
|
|
256
|
-
<span className="menu-shortcut">⌘ W</span>
|
|
257
|
-
</button>
|
|
258
|
-
)}
|
|
259
|
-
{onClearAll && (
|
|
260
|
-
<button className="menu-item" onClick={() => handleMenuClick(onClearAll)}>
|
|
261
|
-
清空所有对话
|
|
262
|
-
</button>
|
|
263
|
-
)}
|
|
264
|
-
{onCloseOthers && (
|
|
265
|
-
<button className="menu-item" onClick={() => handleMenuClick(onCloseOthers)}>
|
|
266
|
-
关闭其他对话
|
|
267
|
-
</button>
|
|
268
|
-
)}
|
|
269
|
-
|
|
270
|
-
{(showClose || onClearAll || onCloseOthers) && <div className="menu-divider" />}
|
|
271
|
-
|
|
272
|
-
{onExport && (
|
|
273
|
-
<button className="menu-item" onClick={() => handleMenuClick(onExport)}>
|
|
274
|
-
导出对话
|
|
275
|
-
</button>
|
|
276
|
-
)}
|
|
277
|
-
{onCopyId && (
|
|
278
|
-
<button className="menu-item" onClick={() => handleMenuClick(onCopyId)}>
|
|
279
|
-
复制请求 ID
|
|
280
|
-
</button>
|
|
281
|
-
)}
|
|
282
|
-
{onFeedback && (
|
|
283
|
-
<button className="menu-item" onClick={() => handleMenuClick(onFeedback)}>
|
|
284
|
-
反馈
|
|
285
|
-
</button>
|
|
286
|
-
)}
|
|
287
|
-
|
|
288
|
-
{(onExport || onCopyId || onFeedback) && onSettings && <div className="menu-divider" />}
|
|
289
|
-
|
|
290
|
-
{onSettings && (
|
|
291
|
-
<button className="menu-item" onClick={() => handleMenuClick(onSettings)}>
|
|
292
|
-
Agent 设置
|
|
293
|
-
</button>
|
|
294
|
-
)}
|
|
295
|
-
</div>
|
|
296
|
-
)}
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
)
|
|
301
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WelcomeMessage Component
|
|
3
|
-
* 与 Vue 版本 WelcomeMessage.vue 保持一致
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { type FC } from 'react'
|
|
7
|
-
import { Wand2, ImageIcon, Video, Terminal } from 'lucide-react'
|
|
8
|
-
|
|
9
|
-
interface WelcomeMessageProps {
|
|
10
|
-
/** 快捷操作回调 */
|
|
11
|
-
onQuickAction: (text: string) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** 快捷操作配置 */
|
|
15
|
-
const QUICK_ACTIONS = [
|
|
16
|
-
{
|
|
17
|
-
id: 'txt2img',
|
|
18
|
-
Icon: Wand2,
|
|
19
|
-
label: '文生图',
|
|
20
|
-
desc: 'AI 绘制创意图像',
|
|
21
|
-
prompt: '帮我生成一张图片:',
|
|
22
|
-
gradient: 'purple',
|
|
23
|
-
iconColor: 'purple',
|
|
24
|
-
featured: true,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'img2img',
|
|
28
|
-
Icon: ImageIcon,
|
|
29
|
-
label: '图生图',
|
|
30
|
-
desc: '风格迁移',
|
|
31
|
-
prompt: '基于这张图片进行风格转换',
|
|
32
|
-
gradient: 'blue',
|
|
33
|
-
iconColor: 'blue',
|
|
34
|
-
featured: false,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: 'img2vid',
|
|
38
|
-
Icon: Video,
|
|
39
|
-
label: '图生视频',
|
|
40
|
-
desc: '动态化',
|
|
41
|
-
prompt: '将这张图片转换成视频',
|
|
42
|
-
gradient: 'emerald',
|
|
43
|
-
iconColor: 'emerald',
|
|
44
|
-
featured: false,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: 'cmd',
|
|
48
|
-
Icon: Terminal,
|
|
49
|
-
label: '执行命令',
|
|
50
|
-
desc: '系统管理',
|
|
51
|
-
prompt: '执行命令:',
|
|
52
|
-
gradient: 'orange',
|
|
53
|
-
iconColor: 'orange',
|
|
54
|
-
featured: true,
|
|
55
|
-
},
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
export const WelcomeMessage: FC<WelcomeMessageProps> = ({ onQuickAction }) => {
|
|
59
|
-
return (
|
|
60
|
-
<div className="welcome-message">
|
|
61
|
-
{/* 动态极光背景 */}
|
|
62
|
-
<div className="welcome-glow purple" />
|
|
63
|
-
<div className="welcome-glow blue" />
|
|
64
|
-
|
|
65
|
-
{/* 标题区域 */}
|
|
66
|
-
<div className="welcome-title-area">
|
|
67
|
-
<h1 className="welcome-title">
|
|
68
|
-
Create
|
|
69
|
-
<br />
|
|
70
|
-
<span className="welcome-title-accent">Everything</span>
|
|
71
|
-
</h1>
|
|
72
|
-
<p className="welcome-subtitle">释放 AI 的无限创造力</p>
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
{/* 快捷操作网格 */}
|
|
76
|
-
<div className="quick-actions">
|
|
77
|
-
{QUICK_ACTIONS.map((action) => (
|
|
78
|
-
<button
|
|
79
|
-
key={action.id}
|
|
80
|
-
className={`quick-action-btn${action.featured ? ' featured' : ''}`}
|
|
81
|
-
onClick={() => onQuickAction(action.prompt)}
|
|
82
|
-
>
|
|
83
|
-
{/* 卡片背景渐变 */}
|
|
84
|
-
<div className={`quick-action-gradient ${action.gradient}`} />
|
|
85
|
-
|
|
86
|
-
{/* 图标 */}
|
|
87
|
-
<action.Icon className={`quick-action-icon ${action.iconColor}`} />
|
|
88
|
-
|
|
89
|
-
{/* 文字 */}
|
|
90
|
-
<div className="quick-action-text">
|
|
91
|
-
<span className="quick-action-label">{action.label}</span>
|
|
92
|
-
<span className="quick-action-desc">{action.desc}</span>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
{/* 装饰性光斑 */}
|
|
96
|
-
<div className="quick-action-glow" />
|
|
97
|
-
</button>
|
|
98
|
-
))}
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
{/* 底部装饰 */}
|
|
102
|
-
<div className="welcome-footer">
|
|
103
|
-
<div className="welcome-footer-line" />
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|