@huyooo/ai-chat-frontend-react 0.1.6 → 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.
Files changed (91) hide show
  1. package/README.md +368 -0
  2. package/dist/index.css +2575 -0
  3. package/dist/index.css.map +1 -0
  4. package/dist/index.d.ts +378 -135
  5. package/dist/index.js +3956 -1042
  6. package/dist/index.js.map +1 -1
  7. package/dist/style.css +48 -987
  8. package/package.json +7 -4
  9. package/src/adapter.ts +10 -70
  10. package/src/components/ChatPanel.tsx +373 -117
  11. package/src/components/common/ConfirmDialog.css +136 -0
  12. package/src/components/common/ConfirmDialog.tsx +91 -0
  13. package/src/components/common/CopyButton.css +22 -0
  14. package/src/components/common/CopyButton.tsx +46 -0
  15. package/src/components/common/IndexingSettings.css +207 -0
  16. package/src/components/common/IndexingSettings.tsx +398 -0
  17. package/src/components/common/SettingsPanel.css +256 -0
  18. package/src/components/common/SettingsPanel.tsx +120 -0
  19. package/src/components/common/Toast.css +50 -0
  20. package/src/components/common/Toast.tsx +38 -0
  21. package/src/components/common/ToggleSwitch.css +52 -0
  22. package/src/components/common/ToggleSwitch.tsx +20 -0
  23. package/src/components/header/ChatHeader.css +285 -0
  24. package/src/components/header/ChatHeader.tsx +376 -0
  25. package/src/components/input/AtFilePicker.css +147 -0
  26. package/src/components/input/AtFilePicker.tsx +519 -0
  27. package/src/components/input/ChatInput.css +204 -0
  28. package/src/components/input/ChatInput.tsx +506 -0
  29. package/src/components/input/DropdownSelector.css +159 -0
  30. package/src/components/input/DropdownSelector.tsx +195 -0
  31. package/src/components/input/ImagePreviewModal.css +124 -0
  32. package/src/components/input/ImagePreviewModal.tsx +118 -0
  33. package/src/components/input/at-views/AtBranchView.tsx +34 -0
  34. package/src/components/input/at-views/AtBrowserView.tsx +34 -0
  35. package/src/components/input/at-views/AtChatsView.tsx +34 -0
  36. package/src/components/input/at-views/AtDocsView.tsx +34 -0
  37. package/src/components/input/at-views/AtFilesView.tsx +168 -0
  38. package/src/components/input/at-views/AtTerminalsView.tsx +34 -0
  39. package/src/components/input/at-views/AtViewStyles.css +143 -0
  40. package/src/components/input/at-views/index.ts +9 -0
  41. package/src/components/message/ContentRenderer.css +9 -0
  42. package/src/components/message/ContentRenderer.tsx +63 -0
  43. package/src/components/message/MessageBubble.css +190 -0
  44. package/src/components/message/MessageBubble.tsx +231 -0
  45. package/src/components/message/PartsRenderer.css +4 -0
  46. package/src/components/message/PartsRenderer.tsx +114 -0
  47. package/src/components/message/ToolResultRenderer.tsx +21 -0
  48. package/src/components/message/WelcomeMessage.css +221 -0
  49. package/src/components/message/WelcomeMessage.tsx +93 -0
  50. package/src/components/message/blocks/CodeBlock.tsx +60 -0
  51. package/src/components/message/blocks/TextBlock.tsx +15 -0
  52. package/src/components/message/blocks/blocks.css +141 -0
  53. package/src/components/message/blocks/index.ts +6 -0
  54. package/src/components/message/parts/CollapsibleCard.css +78 -0
  55. package/src/components/message/parts/CollapsibleCard.tsx +77 -0
  56. package/src/components/message/parts/ErrorPart.css +9 -0
  57. package/src/components/message/parts/ErrorPart.tsx +40 -0
  58. package/src/components/message/parts/ImagePart.css +50 -0
  59. package/src/components/message/parts/ImagePart.tsx +54 -0
  60. package/src/components/message/parts/SearchPart.css +44 -0
  61. package/src/components/message/parts/SearchPart.tsx +63 -0
  62. package/src/components/message/parts/TextPart.css +10 -0
  63. package/src/components/message/parts/TextPart.tsx +20 -0
  64. package/src/components/message/parts/ThinkingPart.css +9 -0
  65. package/src/components/message/parts/ThinkingPart.tsx +48 -0
  66. package/src/components/message/parts/ToolCallPart.css +220 -0
  67. package/src/components/message/parts/ToolCallPart.tsx +285 -0
  68. package/src/components/message/parts/ToolResultPart.css +68 -0
  69. package/src/components/message/parts/ToolResultPart.tsx +96 -0
  70. package/src/components/message/parts/index.ts +11 -0
  71. package/src/components/message/tool-results/DefaultToolResult.tsx +26 -0
  72. package/src/components/message/tool-results/SearchResults.tsx +69 -0
  73. package/src/components/message/tool-results/WeatherCard.tsx +63 -0
  74. package/src/components/message/tool-results/index.ts +7 -0
  75. package/src/components/message/tool-results/tool-results.css +179 -0
  76. package/src/components/message/welcome-types.ts +46 -0
  77. package/src/context/AutoRunConfigContext.tsx +13 -0
  78. package/src/context/ChatAdapterContext.tsx +8 -0
  79. package/src/context/ChatInputContext.tsx +40 -0
  80. package/src/context/RenderersContext.tsx +41 -0
  81. package/src/hooks/useChat.ts +855 -237
  82. package/src/hooks/useImageUpload.ts +253 -0
  83. package/src/index.ts +96 -39
  84. package/src/styles.css +48 -987
  85. package/src/types/index.ts +172 -103
  86. package/src/utils/fileIcon.ts +49 -0
  87. package/src/components/ChatInput.tsx +0 -368
  88. package/src/components/chat/messages/ExecutionSteps.tsx +0 -234
  89. package/src/components/chat/messages/MessageBubble.tsx +0 -130
  90. package/src/components/chat/ui/ChatHeader.tsx +0 -301
  91. 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
- }