@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
@@ -0,0 +1,120 @@
1
+ /**
2
+ * 设置面板组件
3
+ * 用于配置 AutoRunConfig
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from 'react'
7
+ import { Icon } from '@iconify/react'
8
+ import type { AutoRunConfig, AutoRunMode } from '@huyooo/ai-chat-bridge-electron/renderer'
9
+ import { DropdownSelector, type DropdownOption } from '../input/DropdownSelector'
10
+ import { ToggleSwitch } from './ToggleSwitch'
11
+ import { IndexingSettings } from './IndexingSettings'
12
+ import './SettingsPanel.css'
13
+
14
+ interface SettingsPanelProps {
15
+ visible: boolean
16
+ config: AutoRunConfig
17
+ onClose: () => void
18
+ onChange: (config: AutoRunConfig) => void
19
+ }
20
+
21
+ const sections = [
22
+ { id: 'agent', label: 'Agent', icon: 'solar:link-round-angle-line-duotone' },
23
+ { id: 'indexing', label: '索引与文档', icon: 'lucide:database' },
24
+ ] as const
25
+
26
+ export function SettingsPanel({ visible, config, onClose, onChange }: SettingsPanelProps) {
27
+ const [currentSection, setCurrentSection] = useState<string>('agent')
28
+
29
+ const currentSectionLabel = useMemo(
30
+ () => sections.find(s => s.id === currentSection)?.label ?? '',
31
+ [currentSection]
32
+ )
33
+
34
+ // 模式选项
35
+ const modeOptions: DropdownOption[] = [
36
+ { value: 'run-everything', label: '运行所有内容(自动执行)' },
37
+ { value: 'manual', label: '手动批准(每次执行前询问)' },
38
+ ]
39
+
40
+ /** 更新配置项 */
41
+ const updateConfig = useCallback(<K extends keyof AutoRunConfig>(key: K, value: AutoRunConfig[K]) => {
42
+ onChange({ ...config, [key]: value })
43
+ }, [config, onChange])
44
+
45
+ /** 处理模式变化 */
46
+ const handleModeChange = useCallback((value: string) => {
47
+ updateConfig('mode', value as AutoRunMode)
48
+ }, [updateConfig])
49
+
50
+ /** 处理遮罩点击 */
51
+ const handleOverlayClick = useCallback((e: React.MouseEvent) => {
52
+ if (e.target === e.currentTarget) {
53
+ onClose()
54
+ }
55
+ }, [onClose])
56
+
57
+ if (!visible) return null
58
+
59
+ return (
60
+ <div className="settings-panel-overlay" onClick={handleOverlayClick}>
61
+ <div className="settings-panel">
62
+ {/* 左侧导航 */}
63
+ <div className="settings-sidebar">
64
+ <div className="sidebar-header">
65
+ <h3 className="sidebar-title">设置</h3>
66
+ </div>
67
+ <div className="sidebar-content">
68
+ {sections.map((section) => (
69
+ <button
70
+ key={section.id}
71
+ className={`sidebar-item${currentSection === section.id ? ' active' : ''}`}
72
+ onClick={() => setCurrentSection(section.id)}
73
+ >
74
+ <Icon icon={section.icon} width={18} />
75
+ <span>{section.label}</span>
76
+ </button>
77
+ ))}
78
+ </div>
79
+ </div>
80
+
81
+ {/* 右侧内容 */}
82
+ <div className="settings-content">
83
+ <div className="content-header">
84
+ <h2 className="content-title">{currentSectionLabel}</h2>
85
+ <button className="close-btn" onClick={onClose}>
86
+ <Icon icon="lucide:x" width={20} />
87
+ </button>
88
+ </div>
89
+ <div className="content-body">
90
+ {/* Agent 设置 */}
91
+ {currentSection === 'agent' && (
92
+ <>
93
+ {/* 自动运行模式 */}
94
+ <div className="setting-item">
95
+ <div className="setting-info">
96
+ <div className="setting-label">自动运行模式</div>
97
+ <div className="setting-description">控制工具执行的自动运行行为</div>
98
+ </div>
99
+ <div className="setting-control">
100
+ <DropdownSelector
101
+ value={config.mode ?? 'run-everything'}
102
+ options={modeOptions}
103
+ align="right"
104
+ onSelect={handleModeChange}
105
+ />
106
+ </div>
107
+ </div>
108
+ </>
109
+ )}
110
+
111
+ {/* 索引与文档设置 */}
112
+ {currentSection === 'indexing' && (
113
+ <IndexingSettings />
114
+ )}
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ )
120
+ }
@@ -0,0 +1,50 @@
1
+ .toast-container {
2
+ position: fixed;
3
+ top: 60px;
4
+ left: 50%;
5
+ transform: translateX(-50%);
6
+ z-index: 10000;
7
+ pointer-events: none;
8
+ animation: toast-in 0.2s ease;
9
+ }
10
+
11
+ .toast {
12
+ padding: 8px 16px;
13
+ border-radius: 6px;
14
+ font-size: 13px;
15
+ border: 1px solid;
16
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
17
+ }
18
+
19
+ /* 正常/成功 - 灰色 */
20
+ .toast-info,
21
+ .toast-success {
22
+ background: var(--chat-bg-secondary, #2a2a2a);
23
+ color: var(--chat-text, #fff);
24
+ border-color: var(--chat-border, #444);
25
+ }
26
+
27
+ /* 警告 - 黄色 */
28
+ .toast-warning {
29
+ background: rgba(245, 158, 11, 0.15);
30
+ color: #f59e0b;
31
+ border-color: rgba(245, 158, 11, 0.3);
32
+ }
33
+
34
+ /* 错误 - 红色 */
35
+ .toast-error {
36
+ background: rgba(239, 68, 68, 0.15);
37
+ color: #ef4444;
38
+ border-color: rgba(239, 68, 68, 0.3);
39
+ }
40
+
41
+ @keyframes toast-in {
42
+ from {
43
+ opacity: 0;
44
+ transform: translateX(-50%) translateY(-10px);
45
+ }
46
+ to {
47
+ opacity: 1;
48
+ transform: translateX(-50%) translateY(0);
49
+ }
50
+ }
@@ -0,0 +1,38 @@
1
+ import { FC, useEffect } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import './Toast.css'
4
+
5
+ interface ToastProps {
6
+ visible: boolean
7
+ message: string
8
+ type?: 'info' | 'success' | 'warning' | 'error'
9
+ duration?: number
10
+ onClose: () => void
11
+ }
12
+
13
+ export const Toast: FC<ToastProps> = ({
14
+ visible,
15
+ message,
16
+ type = 'info',
17
+ duration = 2000,
18
+ onClose,
19
+ }) => {
20
+ // 自动关闭
21
+ useEffect(() => {
22
+ if (visible && duration > 0) {
23
+ const timer = setTimeout(() => {
24
+ onClose()
25
+ }, duration)
26
+ return () => clearTimeout(timer)
27
+ }
28
+ }, [visible, duration, onClose])
29
+
30
+ if (!visible) return null
31
+
32
+ return createPortal(
33
+ <div className="toast-container">
34
+ <div className={`toast toast-${type}`}>{message}</div>
35
+ </div>,
36
+ document.body
37
+ )
38
+ }
@@ -0,0 +1,52 @@
1
+ .toggle-switch {
2
+ position: relative;
3
+ display: inline-block;
4
+ width: 44px;
5
+ height: 24px;
6
+ cursor: pointer;
7
+ }
8
+
9
+ .toggle-switch input {
10
+ opacity: 0;
11
+ width: 0;
12
+ height: 0;
13
+ }
14
+
15
+ .toggle-slider {
16
+ position: absolute;
17
+ top: 0;
18
+ left: 0;
19
+ right: 0;
20
+ bottom: 0;
21
+ background-color: var(--chat-muted, #3c3c3c);
22
+ border-radius: 24px;
23
+ transition: all 0.2s;
24
+ border: 1px solid rgba(255, 255, 255, 0.1);
25
+ }
26
+
27
+ .toggle-slider::before {
28
+ position: absolute;
29
+ content: "";
30
+ height: 18px;
31
+ width: 18px;
32
+ left: 3px;
33
+ top: 50%;
34
+ transform: translateY(-50%);
35
+ background-color: #fff;
36
+ border-radius: 50%;
37
+ transition: all 0.2s;
38
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
39
+ }
40
+
41
+ .toggle-switch input:checked + .toggle-slider {
42
+ background-color: rgb(153, 207, 140);
43
+ border-color: rgba(255, 255, 255, 0.2);
44
+ }
45
+
46
+ .toggle-switch input:checked + .toggle-slider::before {
47
+ transform: translate(20px, -50%);
48
+ }
49
+
50
+ .toggle-switch input:focus + .toggle-slider {
51
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
52
+ }
@@ -0,0 +1,20 @@
1
+ import type { FC } from 'react'
2
+ import './ToggleSwitch.css'
3
+
4
+ interface ToggleSwitchProps {
5
+ checked?: boolean
6
+ onChange: (value: boolean) => void
7
+ }
8
+
9
+ export const ToggleSwitch: FC<ToggleSwitchProps> = ({ checked = false, onChange }) => {
10
+ return (
11
+ <label className="toggle-switch">
12
+ <input
13
+ type="checkbox"
14
+ checked={checked}
15
+ onChange={(e) => onChange(e.target.checked)}
16
+ />
17
+ <span className="toggle-slider"></span>
18
+ </label>
19
+ )
20
+ }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * ChatHeader 组件样式
3
+ */
4
+
5
+ .chat-header {
6
+ display: flex;
7
+ align-items: center;
8
+ height: 40px;
9
+ padding: 0 12px;
10
+ background: var(--chat-header-bg, #1e1e1e);
11
+ border-bottom: 1px solid var(--chat-border, #333);
12
+ flex-shrink: 0;
13
+ }
14
+
15
+ .tabs-container {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 4px;
19
+ flex: 1;
20
+ overflow-x: auto;
21
+ padding-right: 8px;
22
+ }
23
+
24
+ .tabs-container::-webkit-scrollbar {
25
+ display: none;
26
+ }
27
+
28
+ .tab-item {
29
+ position: relative;
30
+ display: flex;
31
+ align-items: center;
32
+ padding: 4px 8px;
33
+ background: transparent;
34
+ border: none;
35
+ border-radius: 4px;
36
+ color: var(--chat-text-muted, #888);
37
+ font-size: 14px;
38
+ font-weight: 500;
39
+ cursor: pointer;
40
+ transition: all 0.15s;
41
+ white-space: nowrap;
42
+ max-width: 120px;
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .tab-item:hover {
47
+ color: var(--chat-text, #ccc);
48
+ }
49
+
50
+ .tab-item.active {
51
+ background: var(--chat-muted, #3c3c3c);
52
+ color: var(--chat-text, #fff);
53
+ }
54
+
55
+ .tab-title {
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ white-space: nowrap;
59
+ }
60
+
61
+ .tab-close {
62
+ position: absolute;
63
+ right: 0;
64
+ top: 0;
65
+ bottom: 0;
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 32px;
70
+ padding: 0;
71
+ padding-left: 8px;
72
+ background: linear-gradient(to right, transparent, var(--chat-bg, #1e1e1e) 50%);
73
+ border: none;
74
+ border-radius: 0 4px 4px 0;
75
+ color: var(--chat-text-muted, #666);
76
+ cursor: pointer;
77
+ transition: all 0.15s;
78
+ opacity: 0;
79
+ }
80
+
81
+ .tab-item:hover .tab-close {
82
+ opacity: 1;
83
+ }
84
+
85
+ .tab-item.active .tab-close {
86
+ background: linear-gradient(to right, transparent, var(--chat-muted, #3c3c3c) 50%);
87
+ }
88
+
89
+ .tab-close:hover {
90
+ color: var(--chat-text, #fff);
91
+ }
92
+
93
+ .header-actions {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 2px;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .header-actions .icon-btn {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ width: 24px;
105
+ height: 24px;
106
+ padding: 0;
107
+ background: transparent;
108
+ border: none;
109
+ border-radius: 4px;
110
+ color: var(--chat-text-muted, #888);
111
+ cursor: pointer;
112
+ transition: all 0.15s;
113
+ }
114
+
115
+ .header-actions .icon-btn:hover,
116
+ .header-actions .icon-btn.active {
117
+ color: var(--chat-text, #fff);
118
+ }
119
+
120
+ .header-actions .icon-btn.small {
121
+ width: 20px;
122
+ height: 20px;
123
+ }
124
+
125
+ .dropdown-container {
126
+ position: relative;
127
+ }
128
+
129
+ .dropdown-panel {
130
+ position: absolute;
131
+ top: 100%;
132
+ right: 0;
133
+ margin-top: 4px;
134
+ background: var(--chat-dropdown-bg, #252526);
135
+ border: 1px solid rgba(255, 255, 255, 0.1);
136
+ border-radius: 8px;
137
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
138
+ z-index: 100;
139
+ overflow: hidden;
140
+ }
141
+
142
+ .history-panel {
143
+ width: 360px;
144
+ }
145
+
146
+ .more-panel {
147
+ width: 200px;
148
+ padding: 4px;
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 2px;
152
+ }
153
+
154
+ .panel-header {
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: space-between;
158
+ padding: 10px 12px;
159
+ border-bottom: 1px solid var(--chat-border, #333);
160
+ font-size: 14px;
161
+ font-weight: 500;
162
+ color: var(--chat-text-muted, #888);
163
+ }
164
+
165
+ .panel-content {
166
+ max-height: 400px;
167
+ overflow-y: auto;
168
+ }
169
+
170
+ /* 日期分组标签 */
171
+ .history-group-label {
172
+ padding: 8px 12px 4px;
173
+ font-size: 14px;
174
+ font-weight: 500;
175
+ color: var(--chat-text-muted, #666);
176
+ }
177
+
178
+ .history-group-label:not(:first-child) {
179
+ border-top: 1px solid var(--chat-border, #333);
180
+ margin-top: 4px;
181
+ padding-top: 12px;
182
+ }
183
+
184
+ .history-item {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 4px;
188
+ padding: 6px 12px;
189
+ cursor: pointer;
190
+ transition: background 0.15s;
191
+ }
192
+
193
+ .history-item:hover {
194
+ background: rgba(255, 255, 255, 0.08);
195
+ }
196
+
197
+ .history-item.active {
198
+ background: rgba(255, 255, 255, 0.1);
199
+ }
200
+
201
+ .history-title {
202
+ flex: 1;
203
+ font-size: 14px;
204
+ color: var(--chat-text, #ccc);
205
+ overflow: hidden;
206
+ text-overflow: ellipsis;
207
+ white-space: nowrap;
208
+ }
209
+
210
+ .history-time {
211
+ font-size: 14px;
212
+ color: var(--chat-text-muted, #666);
213
+ flex-shrink: 0;
214
+ margin-right: 10px;
215
+ }
216
+
217
+ .history-actions {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 2px;
221
+ flex-shrink: 0;
222
+ opacity: 0;
223
+ transition: opacity 0.15s;
224
+ }
225
+
226
+ .history-item:hover .history-actions {
227
+ opacity: 1;
228
+ }
229
+
230
+ .history-action-btn {
231
+ display: flex;
232
+ align-items: center;
233
+ justify-content: center;
234
+ width: 28px;
235
+ height: 28px;
236
+ padding: 0;
237
+ background: transparent;
238
+ border: none;
239
+ border-radius: 6px;
240
+ color: var(--chat-text-muted, #666);
241
+ cursor: pointer;
242
+ transition: all 0.15s;
243
+ }
244
+
245
+ .history-action-btn:hover {
246
+ color: var(--chat-text, #fff);
247
+ }
248
+
249
+ .history-action-btn.delete:hover {
250
+ color: var(--chat-destructive, #ef4444);
251
+ }
252
+
253
+ .empty-state {
254
+ padding: 20px;
255
+ text-align: center;
256
+ font-size: 14px;
257
+ color: var(--chat-text-muted, #666);
258
+ }
259
+
260
+ .menu-item {
261
+ display: flex;
262
+ align-items: center;
263
+ gap: 8px;
264
+ width: 100%;
265
+ padding: 8px 10px;
266
+ border: none;
267
+ background: transparent;
268
+ border-radius: 4px;
269
+ font-size: 14px;
270
+ color: var(--chat-text-muted, #999);
271
+ cursor: pointer;
272
+ transition: all 0.15s;
273
+ text-align: left;
274
+ }
275
+
276
+ .menu-item:hover {
277
+ background: rgba(255, 255, 255, 0.08);
278
+ color: var(--chat-text, #ccc);
279
+ }
280
+
281
+ .menu-divider {
282
+ height: 1px;
283
+ background: var(--chat-border, #333);
284
+ margin: 4px 0;
285
+ }